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

@@ -0,0 +1,204 @@
@props([
'item' => null,
'config' => [],
'badges' => [],
'colorScheme' => ['primary' => 'blue', 'secondary' => 'green', 'accent' => 'purple'],
'layout' => 'grid'
])
@php
$cardConfig = collect($config);
$badgeConfig = collect($badges);
@endphp
@if($layout === 'grid')
{{-- Grid Layout Card --}}
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 border border-gray-200 dark:border-gray-700 hover:shadow-lg transition-shadow">
{{-- Header --}}
<div class="flex items-start justify-between mb-4">
<div class="flex-1">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">
{{ data_get($item, $cardConfig->get('title', 'name')) }}
</h3>
@if($cardConfig->has('subtitle') && data_get($item, $cardConfig->get('subtitle')))
<p class="text-sm text-gray-600 dark:text-gray-400">
{{ data_get($item, $cardConfig->get('subtitle')) }}
</p>
@endif
</div>
@if($cardConfig->has('score') && data_get($item, $cardConfig->get('score')))
<div class="text-right">
<div class="text-lg font-bold text-{{ $colorScheme['primary'] }}-600 dark:text-{{ $colorScheme['primary'] }}-400">
{{ data_get($item, $cardConfig->get('score')) }}
</div>
<div class="text-xs text-gray-500">{{ $cardConfig->get('scoreLabel', 'Score') }}</div>
</div>
@endif
</div>
{{-- Badges --}}
@if($badgeConfig->has('fields'))
<div class="flex flex-wrap gap-2 mb-4">
@foreach($badgeConfig->get('fields', []) as $badgeField)
@if(data_get($item, $badgeField['field']))
<span class="px-3 py-1 text-sm bg-{{ $badgeField['color'] ?? $colorScheme['primary'] }}-100 dark:bg-{{ $badgeField['color'] ?? $colorScheme['primary'] }}-900 text-{{ $badgeField['color'] ?? $colorScheme['primary'] }}-800 dark:text-{{ $badgeField['color'] ?? $colorScheme['primary'] }}-200 rounded-full">
{{ $badgeField['prefix'] ?? '' }}{{ data_get($item, $badgeField['field']) }}{{ $badgeField['suffix'] ?? '' }}
</span>
@endif
@endforeach
</div>
@endif
{{-- Metrics --}}
@if($cardConfig->has('metrics'))
<div class="grid grid-cols-2 gap-4 text-sm">
@foreach(array_slice($cardConfig->get('metrics', []), 0, 4) as $metric)
@if(data_get($item, $metric['field']))
<div>
<div class="font-semibold text-gray-900 dark:text-gray-100">
{{ isset($metric['format']) ? sprintf($metric['format'], data_get($item, $metric['field'])) : data_get($item, $metric['field']) }}
</div>
<div class="text-gray-600 dark:text-gray-400">{{ $metric['label'] }}</div>
</div>
@endif
@endforeach
</div>
@endif
</div>
@elseif($layout === 'list')
{{-- List Layout Card --}}
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 border border-gray-200 dark:border-gray-700 hover:shadow-lg transition-shadow">
<div class="flex items-start justify-between">
<div class="flex-1">
<div class="flex items-start justify-between mb-3">
<div class="flex-1">
<h3 class="text-xl font-semibold text-gray-900 dark:text-gray-100">
{{ data_get($item, $cardConfig->get('title', 'name')) }}
</h3>
@if($cardConfig->has('description') && data_get($item, $cardConfig->get('description')))
<p class="text-gray-600 dark:text-gray-400 mt-1">
{{ Str::limit(data_get($item, $cardConfig->get('description')), 150) }}
</p>
@endif
</div>
@if($cardConfig->has('score') && data_get($item, $cardConfig->get('score')))
<div class="text-right ml-6">
<div class="text-2xl font-bold text-{{ $colorScheme['primary'] }}-600 dark:text-{{ $colorScheme['primary'] }}-400">
{{ data_get($item, $cardConfig->get('score')) }}
</div>
<div class="text-sm text-gray-500">{{ $cardConfig->get('scoreLabel', 'Score') }}</div>
</div>
@endif
</div>
{{-- Badges --}}
@if($badgeConfig->has('fields'))
<div class="flex flex-wrap gap-2 mb-4">
@foreach($badgeConfig->get('fields', []) as $badgeField)
@if(data_get($item, $badgeField['field']))
<span class="px-3 py-1 text-sm bg-{{ $badgeField['color'] ?? $colorScheme['primary'] }}-100 dark:bg-{{ $badgeField['color'] ?? $colorScheme['primary'] }}-900 text-{{ $badgeField['color'] ?? $colorScheme['primary'] }}-800 dark:text-{{ $badgeField['color'] ?? $colorScheme['primary'] }}-200 rounded-full">
{{ $badgeField['prefix'] ?? '' }}{{ data_get($item, $badgeField['field']) }}{{ $badgeField['suffix'] ?? '' }}
</span>
@endif
@endforeach
</div>
@endif
{{-- Metrics --}}
@if($cardConfig->has('metrics'))
<div class="grid grid-cols-4 gap-6 text-sm">
@foreach($cardConfig->get('metrics', []) as $metric)
@if(data_get($item, $metric['field']))
<div>
<div class="text-lg font-semibold text-gray-900 dark:text-gray-100">
{{ isset($metric['format']) ? sprintf($metric['format'], data_get($item, $metric['field'])) : data_get($item, $metric['field']) }}
</div>
<div class="text-gray-600 dark:text-gray-400">{{ $metric['label'] }}</div>
</div>
@endif
@endforeach
</div>
@endif
</div>
</div>
</div>
@elseif($layout === 'portfolio')
{{-- Portfolio Layout Card --}}
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-8 border border-gray-200 dark:border-gray-700 hover:shadow-lg transition-shadow">
<div class="flex items-start justify-between mb-6">
<div class="flex-1">
<h3 class="text-2xl font-semibold text-gray-900 dark:text-gray-100 mb-3">
{{ data_get($item, $cardConfig->get('title', 'name')) }}
</h3>
@if($cardConfig->has('description') && data_get($item, $cardConfig->get('description')))
<p class="text-gray-600 dark:text-gray-400 mb-4 text-lg">
{{ data_get($item, $cardConfig->get('description')) }}
</p>
@endif
{{-- Enhanced Badges for Portfolio --}}
@if($badgeConfig->has('fields'))
<div class="flex flex-wrap gap-3">
@foreach($badgeConfig->get('fields', []) as $badgeField)
@if(data_get($item, $badgeField['field']))
<span class="px-4 py-2 text-sm bg-{{ $badgeField['color'] ?? $colorScheme['primary'] }}-100 dark:bg-{{ $badgeField['color'] ?? $colorScheme['primary'] }}-900 text-{{ $badgeField['color'] ?? $colorScheme['primary'] }}-800 dark:text-{{ $badgeField['color'] ?? $colorScheme['primary'] }}-200 rounded-full font-medium">
{{ $badgeField['prefix'] ?? '' }}{{ data_get($item, $badgeField['field']) }}{{ $badgeField['suffix'] ?? '' }}
</span>
@endif
@endforeach
</div>
@endif
</div>
@if($cardConfig->has('score') && data_get($item, $cardConfig->get('score')))
<div class="text-right ml-8">
<div class="text-3xl font-bold text-{{ $colorScheme['primary'] }}-600 dark:text-{{ $colorScheme['primary'] }}-400">
{{ data_get($item, $cardConfig->get('score')) }}
</div>
<div class="text-sm text-gray-500">{{ $cardConfig->get('scoreLabel', 'Score') }}</div>
</div>
@endif
</div>
{{-- Enhanced Metrics Grid for Portfolio --}}
@if($cardConfig->has('metrics'))
<div class="grid grid-cols-4 gap-8 text-sm border-t border-gray-200 dark:border-gray-700 pt-6">
@foreach($cardConfig->get('metrics', []) as $metric)
@if(data_get($item, $metric['field']))
<div class="text-center">
<div class="text-2xl font-bold text-gray-900 dark:text-gray-100">
{{ isset($metric['format']) ? sprintf($metric['format'], data_get($item, $metric['field'])) : data_get($item, $metric['field']) }}
</div>
<div class="text-gray-600 dark:text-gray-400 mt-1">{{ $metric['label'] }}</div>
</div>
@endif
@endforeach
</div>
@endif
</div>
@else
{{-- Default/Compact Layout --}}
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-4 border border-gray-200 dark:border-gray-700 hover:shadow-lg transition-shadow">
<div class="flex items-center justify-between">
<div class="flex-1">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">
{{ data_get($item, $cardConfig->get('title', 'name')) }}
</h3>
@if($cardConfig->has('subtitle') && data_get($item, $cardConfig->get('subtitle')))
<p class="text-sm text-gray-600 dark:text-gray-400">
{{ data_get($item, $cardConfig->get('subtitle')) }}
</p>
@endif
</div>
@if($cardConfig->has('score') && data_get($item, $cardConfig->get('score')))
<div class="text-right">
<div class="text-lg font-bold text-{{ $colorScheme['primary'] }}-600 dark:text-{{ $colorScheme['primary'] }}-400">
{{ data_get($item, $cardConfig->get('score')) }}
</div>
</div>
@endif
</div>
</div>
@endif

View File

@@ -0,0 +1,513 @@
@props([
'entityType' => 'items',
'entityConfig' => [],
'items' => collect(),
'filters' => [],
'statistics' => [],
'viewModes' => ['grid', 'list'],
'currentViewMode' => 'grid',
'searchPlaceholder' => 'Search...',
'title' => 'Items',
'description' => 'Browse and discover items',
'emptyStateMessage' => 'No items found',
'emptyStateDescription' => 'Try adjusting your search or filters.',
'livewireComponent' => null
])
@php
$config = collect($entityConfig);
$cardFields = $config->get('cardFields', []);
$filterConfig = $config->get('filters', []);
$statisticsConfig = $config->get('statistics', []);
$badgeConfig = $config->get('badges', []);
$sortOptions = $config->get('sortOptions', []);
$colorScheme = $config->get('colorScheme', [
'primary' => 'blue',
'secondary' => 'green',
'accent' => 'purple'
]);
@endphp
<div class="universal-listing-container" x-data="{ viewMode: '{{ $currentViewMode }}' }">
{{-- Mobile Layout (320px - 767px) --}}
<div class="block md:hidden">
{{-- Mobile Header with Search --}}
<div class="sticky top-0 bg-white dark:bg-gray-900 z-20 p-4 border-b border-gray-200 dark:border-gray-700">
<div class="space-y-3">
{{-- Search Input --}}
<div class="relative">
<input
type="text"
@if($livewireComponent) wire:model.live.debounce.300ms="search" @endif
placeholder="{{ $searchPlaceholder }}"
class="w-full pl-10 pr-4 py-3 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-{{ $colorScheme['primary'] }}-500 focus:border-transparent"
>
<svg class="absolute left-3 top-3.5 h-5 w-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
</svg>
</div>
{{-- Quick Filter Buttons --}}
@if(isset($filterConfig['quickFilters']))
<div class="flex flex-wrap gap-2">
@foreach($filterConfig['quickFilters'] as $filter)
<button
@if($livewireComponent) wire:click="toggleFilter('{{ $filter['key'] }}', '{{ $filter['value'] }}')" @endif
class="px-3 py-1.5 text-sm rounded-full border transition-colors {{ $filter['active'] ?? false ? 'bg-' . $colorScheme['primary'] . '-500 text-white border-' . $colorScheme['primary'] . '-500' : 'bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 border-gray-300 dark:border-gray-600' }}"
>
{{ $filter['label'] }}
@if(isset($filter['count']))
<span class="ml-1 text-xs opacity-75">({{ $filter['count'] }})</span>
@endif
</button>
@endforeach
</div>
@endif
</div>
</div>
{{-- Statistics Banner --}}
@if(!empty($statistics))
<div class="bg-gradient-to-r from-{{ $colorScheme['primary'] }}-500 to-{{ $colorScheme['accent'] }}-600 text-white p-4 m-4 rounded-lg">
<div class="text-center">
<h3 class="text-lg font-semibold mb-2">{{ $statistics['title'] ?? 'Overview' }}</h3>
<div class="grid grid-cols-2 gap-4 text-sm">
@foreach(array_slice($statistics['items'] ?? [], 0, 2) as $stat)
<div>
<div class="text-2xl font-bold">{{ $stat['value'] }}</div>
<div class="opacity-90">{{ $stat['label'] }}</div>
</div>
@endforeach
</div>
</div>
</div>
@endif
{{-- Item Cards --}}
<div class="space-y-4 p-4">
@forelse($items as $item)
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-4 border border-gray-200 dark:border-gray-700">
{{-- Item Header --}}
<div class="flex items-start justify-between mb-3">
<div class="flex-1">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">
{{ data_get($item, $cardFields['title'] ?? 'name') }}
</h3>
@if(isset($cardFields['subtitle']) && data_get($item, $cardFields['subtitle']))
<p class="text-sm text-gray-600 dark:text-gray-400">
{{ data_get($item, $cardFields['subtitle']) }}
</p>
@endif
</div>
@if(isset($cardFields['score']) && data_get($item, $cardFields['score']))
<div class="text-right">
<div class="text-sm font-medium text-{{ $colorScheme['primary'] }}-600 dark:text-{{ $colorScheme['primary'] }}-400">
{{ data_get($item, $cardFields['score']) }}
</div>
<div class="text-xs text-gray-500">{{ $cardFields['scoreLabel'] ?? 'Score' }}</div>
</div>
@endif
</div>
{{-- Badges --}}
@if(isset($badgeConfig['fields']))
<div class="flex flex-wrap gap-2 mb-3">
@foreach($badgeConfig['fields'] as $badgeField)
@if(data_get($item, $badgeField['field']))
<span class="px-2 py-1 text-xs bg-{{ $badgeField['color'] ?? $colorScheme['primary'] }}-100 dark:bg-{{ $badgeField['color'] ?? $colorScheme['primary'] }}-900 text-{{ $badgeField['color'] ?? $colorScheme['primary'] }}-800 dark:text-{{ $badgeField['color'] ?? $colorScheme['primary'] }}-200 rounded-full">
{{ $badgeField['prefix'] ?? '' }}{{ data_get($item, $badgeField['field']) }}{{ $badgeField['suffix'] ?? '' }}
</span>
@endif
@endforeach
</div>
@endif
{{-- Key Metrics --}}
@if(isset($cardFields['metrics']))
<div class="grid grid-cols-3 gap-4 text-center text-sm">
@foreach(array_slice($cardFields['metrics'], 0, 3) as $metric)
@if(data_get($item, $metric['field']))
<div>
<div class="font-semibold text-gray-900 dark:text-gray-100">
{{ $metric['format'] ? sprintf($metric['format'], data_get($item, $metric['field'])) : data_get($item, $metric['field']) }}
</div>
<div class="text-gray-600 dark:text-gray-400">{{ $metric['label'] }}</div>
</div>
@endif
@endforeach
</div>
@endif
</div>
@empty
<div class="text-center py-12">
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"></path>
</svg>
<h3 class="mt-2 text-sm font-medium text-gray-900 dark:text-gray-100">{{ $emptyStateMessage }}</h3>
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">{{ $emptyStateDescription }}</p>
</div>
@endforelse
</div>
{{-- Mobile Pagination --}}
@if(method_exists($items, 'hasPages') && $items->hasPages())
<div class="sticky bottom-0 bg-white dark:bg-gray-900 p-4 border-t border-gray-200 dark:border-gray-700">
{{ $items->links('pagination.mobile') }}
</div>
@endif
</div>
{{-- Tablet Layout (768px - 1023px) --}}
<div class="hidden md:block lg:hidden">
<div class="flex h-screen">
{{-- Filter Sidebar --}}
<div class="w-80 bg-gray-50 dark:bg-gray-800 overflow-y-auto border-r border-gray-200 dark:border-gray-700">
<div class="p-6">
{{-- Search --}}
<div class="relative mb-6">
<input
type="text"
@if($livewireComponent) wire:model.live.debounce.300ms="search" @endif
placeholder="{{ $searchPlaceholder }}"
class="w-full pl-10 pr-4 py-3 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"
>
<svg class="absolute left-3 top-3.5 h-5 w-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
</svg>
</div>
{{-- Dynamic Filters --}}
@if(isset($filterConfig['sections']))
@foreach($filterConfig['sections'] as $section)
<div class="mb-6">
<h3 class="text-sm font-medium text-gray-900 dark:text-gray-100 mb-3">{{ $section['title'] }}</h3>
@if($section['type'] === 'checkboxes')
<div class="space-y-2">
@foreach($section['options'] as $option)
<label class="flex items-center">
<input
type="checkbox"
@if($livewireComponent) wire:model.live="{{ $section['model'] }}" @endif
value="{{ $option['value'] }}"
class="rounded border-gray-300 text-{{ $colorScheme['primary'] }}-600 focus:ring-{{ $colorScheme['primary'] }}-500"
>
<span class="ml-2 text-sm text-gray-700 dark:text-gray-300">
{{ $option['label'] }}
@if(isset($option['count']))
({{ $option['count'] }})
@endif
</span>
</label>
@endforeach
</div>
@elseif($section['type'] === 'select')
<select @if($livewireComponent) wire:model.live="{{ $section['model'] }}" @endif class="w-full text-sm border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100">
<option value="">{{ $section['placeholder'] ?? 'All Options' }}</option>
@foreach($section['options'] as $option)
<option value="{{ $option['value'] }}">{{ $option['label'] }}</option>
@endforeach
</select>
@elseif($section['type'] === 'range')
<div class="grid grid-cols-2 gap-2">
<div>
<label class="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">{{ $section['fromLabel'] ?? 'From' }}</label>
<input
type="number"
@if($livewireComponent) wire:model.live="{{ $section['fromModel'] }}" @endif
placeholder="{{ $section['fromPlaceholder'] ?? '' }}"
class="w-full text-sm border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"
>
</div>
<div>
<label class="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">{{ $section['toLabel'] ?? 'To' }}</label>
<input
type="number"
@if($livewireComponent) wire:model.live="{{ $section['toModel'] }}" @endif
placeholder="{{ $section['toPlaceholder'] ?? '' }}"
class="w-full text-sm border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"
>
</div>
</div>
@endif
</div>
@endforeach
@endif
{{-- Statistics Panel --}}
@if(!empty($statistics))
<div class="bg-{{ $colorScheme['primary'] }}-50 dark:bg-{{ $colorScheme['primary'] }}-900/20 rounded-lg p-4">
<h3 class="text-sm font-medium text-{{ $colorScheme['primary'] }}-900 dark:text-{{ $colorScheme['primary'] }}-100 mb-3">{{ $statistics['title'] ?? 'Statistics' }}</h3>
<div class="space-y-2 text-sm">
@foreach($statistics['items'] ?? [] as $stat)
<div class="flex justify-between">
<span class="text-{{ $colorScheme['primary'] }}-700 dark:text-{{ $colorScheme['primary'] }}-300">{{ $stat['label'] }}</span>
<span class="font-medium text-{{ $colorScheme['primary'] }}-900 dark:text-{{ $colorScheme['primary'] }}-100">{{ $stat['value'] }}</span>
</div>
@endforeach
</div>
</div>
@endif
</div>
</div>
{{-- Main Content --}}
<div class="flex-1 flex flex-col">
{{-- Header --}}
<div class="bg-white dark:bg-gray-900 p-6 border-b border-gray-200 dark:border-gray-700">
<div class="flex items-center justify-between">
<div>
<h1 class="text-xl font-semibold text-gray-900 dark:text-gray-100">
{{ method_exists($items, 'total') ? $items->total() : $items->count() }} {{ $title }}
</h1>
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1">{{ $description }}</p>
</div>
<div class="flex items-center space-x-4">
{{-- Sort Selector --}}
@if(!empty($sortOptions))
<select @if($livewireComponent) wire:model.live="sortBy" @endif class="text-sm border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100">
@foreach($sortOptions as $option)
<option value="{{ $option['value'] }}">{{ $option['label'] }}</option>
@endforeach
</select>
@endif
{{-- View Toggle --}}
@if(count($viewModes) > 1)
<div class="flex rounded-md border border-gray-300 dark:border-gray-600">
@foreach($viewModes as $mode)
<button
@if($livewireComponent) wire:click="setViewMode('{{ $mode }}')" @endif
x-on:click="viewMode = '{{ $mode }}'"
class="px-3 py-1 text-sm transition-colors"
:class="viewMode === '{{ $mode }}' ? 'bg-{{ $colorScheme['primary'] }}-500 text-white' : 'bg-white dark:bg-gray-700 text-gray-700 dark:text-gray-300'"
>
{{ ucfirst($mode) }}
</button>
@endforeach
</div>
@endif
</div>
</div>
</div>
{{-- Content Grid --}}
<div class="flex-1 overflow-y-auto p-6">
<div x-show="viewMode === 'grid'" class="grid grid-cols-2 gap-6">
@foreach($items as $item)
<x-universal-listing-card
:item="$item"
:config="$cardFields"
:badges="$badgeConfig"
:colorScheme="$colorScheme"
layout="grid"
/>
@endforeach
</div>
<div x-show="viewMode === 'list'" class="space-y-4">
@foreach($items as $item)
<x-universal-listing-card
:item="$item"
:config="$cardFields"
:badges="$badgeConfig"
:colorScheme="$colorScheme"
layout="list"
/>
@endforeach
</div>
{{-- Pagination --}}
@if(method_exists($items, 'hasPages') && $items->hasPages())
<div class="mt-8">
{{ $items->links() }}
</div>
@endif
</div>
</div>
</div>
</div>
{{-- Desktop Layout (1024px+) --}}
<div class="hidden lg:block">
<div class="flex h-screen">
{{-- Advanced Filter Sidebar --}}
<div class="w-80 bg-gray-50 dark:bg-gray-800 overflow-y-auto border-r border-gray-200 dark:border-gray-700">
<div class="p-6">
{{-- Search --}}
<div class="relative mb-6">
<input
type="text"
@if($livewireComponent) wire:model.live.debounce.300ms="search" @endif
placeholder="{{ $searchPlaceholder }}"
class="w-full pl-10 pr-4 py-3 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"
>
<svg class="absolute left-3 top-3.5 h-5 w-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
</svg>
</div>
{{-- Advanced Filters (Same as tablet but with more options) --}}
@if(isset($filterConfig['sections']))
@foreach($filterConfig['sections'] as $section)
<div class="mb-6">
<h3 class="text-sm font-medium text-gray-900 dark:text-gray-100 mb-3">{{ $section['title'] }}</h3>
@if($section['type'] === 'checkboxes')
<div class="space-y-2">
@foreach($section['options'] as $option)
<label class="flex items-center">
<input
type="checkbox"
@if($livewireComponent) wire:model.live="{{ $section['model'] }}" @endif
value="{{ $option['value'] }}"
class="rounded border-gray-300 text-{{ $colorScheme['primary'] }}-600 focus:ring-{{ $colorScheme['primary'] }}-500"
>
<span class="ml-2 text-sm text-gray-700 dark:text-gray-300">
{{ $option['label'] }}
@if(isset($option['count']))
({{ $option['count'] }})
@endif
</span>
</label>
@endforeach
</div>
@elseif($section['type'] === 'select')
<select @if($livewireComponent) wire:model.live="{{ $section['model'] }}" @endif class="w-full text-sm border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100">
<option value="">{{ $section['placeholder'] ?? 'All Options' }}</option>
@foreach($section['options'] as $option)
<option value="{{ $option['value'] }}">{{ $option['label'] }}</option>
@endforeach
</select>
@elseif($section['type'] === 'range')
<div class="grid grid-cols-2 gap-2">
<div>
<label class="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">{{ $section['fromLabel'] ?? 'From' }}</label>
<input
type="number"
@if($livewireComponent) wire:model.live="{{ $section['fromModel'] }}" @endif
placeholder="{{ $section['fromPlaceholder'] ?? '' }}"
class="w-full text-sm border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"
>
</div>
<div>
<label class="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">{{ $section['toLabel'] ?? 'To' }}</label>
<input
type="number"
@if($livewireComponent) wire:model.live="{{ $section['toModel'] }}" @endif
placeholder="{{ $section['toPlaceholder'] ?? '' }}"
class="w-full text-sm border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"
>
</div>
</div>
@endif
</div>
@endforeach
@endif
{{-- Enhanced Statistics Panel --}}
@if(!empty($statistics))
<div class="bg-{{ $colorScheme['primary'] }}-50 dark:bg-{{ $colorScheme['primary'] }}-900/20 rounded-lg p-4">
<h3 class="text-sm font-medium text-{{ $colorScheme['primary'] }}-900 dark:text-{{ $colorScheme['primary'] }}-100 mb-3">{{ $statistics['title'] ?? 'Statistics' }}</h3>
<div class="space-y-2 text-sm">
@foreach($statistics['items'] ?? [] as $stat)
<div class="flex justify-between">
<span class="text-{{ $colorScheme['primary'] }}-700 dark:text-{{ $colorScheme['primary'] }}-300">{{ $stat['label'] }}</span>
<span class="font-medium text-{{ $colorScheme['primary'] }}-900 dark:text-{{ $colorScheme['primary'] }}-100">{{ $stat['value'] }}</span>
</div>
@endforeach
</div>
</div>
@endif
</div>
</div>
{{-- Main Content Area --}}
<div class="flex-1 flex flex-col">
{{-- Enhanced Header --}}
<div class="bg-white dark:bg-gray-900 p-6 border-b border-gray-200 dark:border-gray-700">
<div class="flex items-center justify-between">
<div>
<h1 class="text-2xl font-semibold text-gray-900 dark:text-gray-100">
{{ method_exists($items, 'total') ? $items->total() : $items->count() }} {{ $title }}
</h1>
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1">{{ $description }}</p>
</div>
<div class="flex items-center space-x-4">
{{-- Sort Selector --}}
@if(!empty($sortOptions))
<select @if($livewireComponent) wire:model.live="sortBy" @endif class="text-sm border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100">
@foreach($sortOptions as $option)
<option value="{{ $option['value'] }}">{{ $option['label'] }}</option>
@endforeach
</select>
@endif
{{-- Enhanced View Toggle --}}
@if(count($viewModes) > 1)
<div class="flex rounded-md border border-gray-300 dark:border-gray-600">
@foreach($viewModes as $mode)
<button
@if($livewireComponent) wire:click="setViewMode('{{ $mode }}')" @endif
x-on:click="viewMode = '{{ $mode }}'"
class="px-4 py-2 text-sm transition-colors"
:class="viewMode === '{{ $mode }}' ? 'bg-{{ $colorScheme['primary'] }}-500 text-white' : 'bg-white dark:bg-gray-700 text-gray-700 dark:text-gray-300'"
>
{{ ucfirst($mode) }}
</button>
@endforeach
</div>
@endif
</div>
</div>
</div>
{{-- Enhanced Content Area --}}
<div class="flex-1 overflow-y-auto p-6">
<div x-show="viewMode === 'grid'" class="grid grid-cols-3 gap-6">
@foreach($items as $item)
<x-universal-listing-card
:item="$item"
:config="$cardFields"
:badges="$badgeConfig"
:colorScheme="$colorScheme"
layout="grid"
/>
@endforeach
</div>
<div x-show="viewMode === 'list'" class="space-y-4">
@foreach($items as $item)
<x-universal-listing-card
:item="$item"
:config="$cardFields"
:badges="$badgeConfig"
:colorScheme="$colorScheme"
layout="list"
/>
@endforeach
</div>
<div x-show="viewMode === 'portfolio'" class="space-y-6">
@foreach($items as $item)
<x-universal-listing-card
:item="$item"
:config="$cardFields"
:badges="$badgeConfig"
:colorScheme="$colorScheme"
layout="portfolio"
/>
@endforeach
</div>
{{-- Pagination --}}
@if(method_exists($items, 'hasPages') && $items->hasPages())
<div class="mt-8">
{{ $items->links() }}
</div>
@endif
</div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,445 @@
<div>
{{-- Universal Listing System Integration --}}
<x-universal-listing
:entity-type="$entityType"
:items="$designers"
:search="$search"
:sort-by="$sortBy"
:sort-direction="$sortDirection"
:view-mode="$viewMode"
:per-page="$perPage"
>
{{-- Custom Creative Portfolio Header --}}
<x-slot name="header">
<div class="bg-gradient-to-r from-purple-500 to-pink-600 text-white p-6 rounded-lg mb-6">
<div class="text-center">
<h2 class="text-2xl font-bold mb-4">Creative Portfolio Overview</h2>
<div class="grid grid-cols-2 md:grid-cols-4 gap-6">
<div class="text-center">
<div class="text-3xl font-bold">{{ $portfolioStats['total_designers'] ?? 0 }}</div>
<div class="text-sm opacity-90">Total Designers</div>
</div>
<div class="text-center">
<div class="text-3xl font-bold">{{ $portfolioStats['coaster_designers'] ?? 0 }}</div>
<div class="text-sm opacity-90">Coaster Designers</div>
</div>
<div class="text-center">
<div class="text-3xl font-bold">{{ $portfolioStats['dark_ride_designers'] ?? 0 }}</div>
<div class="text-sm opacity-90">Dark Ride Designers</div>
</div>
<div class="text-center">
<div class="text-3xl font-bold">{{ number_format($portfolioStats['average_innovation_score'] ?? 0, 1) }}</div>
<div class="text-sm opacity-90">Avg Innovation Score</div>
</div>
</div>
</div>
</div>
</x-slot>
{{-- Custom Search Placeholder --}}
<x-slot name="search-placeholder">
Search designers, specialties, projects...
</x-slot>
{{-- Custom Filters Sidebar --}}
<x-slot name="filters">
{{-- Specialty Filters --}}
<div class="mb-6">
<h3 class="text-sm font-medium text-gray-900 dark:text-gray-100 mb-3">Design Specialties</h3>
<div class="space-y-2">
<label class="flex items-center">
<input
type="checkbox"
wire:model.live="specialties"
value="roller_coaster"
class="rounded border-gray-300 text-purple-600 focus:ring-purple-500"
>
<span class="ml-2 text-sm text-gray-700 dark:text-gray-300">
Roller Coasters ({{ $portfolioStats['coaster_designers'] ?? 0 }})
</span>
</label>
<label class="flex items-center">
<input
type="checkbox"
wire:model.live="specialties"
value="dark_ride"
class="rounded border-gray-300 text-indigo-600 focus:ring-indigo-500"
>
<span class="ml-2 text-sm text-gray-700 dark:text-gray-300">
Dark Rides ({{ $portfolioStats['dark_ride_designers'] ?? 0 }})
</span>
</label>
<label class="flex items-center">
<input
type="checkbox"
wire:model.live="specialties"
value="themed_experience"
class="rounded border-gray-300 text-pink-600 focus:ring-pink-500"
>
<span class="ml-2 text-sm text-gray-700 dark:text-gray-300">
Themed Experiences ({{ $portfolioStats['themed_experience_designers'] ?? 0 }})
</span>
</label>
<label class="flex items-center">
<input
type="checkbox"
wire:model.live="specialties"
value="water_attraction"
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
>
<span class="ml-2 text-sm text-gray-700 dark:text-gray-300">
Water Attractions ({{ $portfolioStats['water_attraction_designers'] ?? 0 }})
</span>
</label>
</div>
</div>
{{-- Creative Filters --}}
<div class="mb-6">
<h3 class="text-sm font-medium text-gray-900 dark:text-gray-100 mb-3">Creative Filters</h3>
<div class="space-y-4">
{{-- Design Style --}}
<div>
<label class="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">Design Style</label>
<select wire:model.live="designStyle" class="w-full text-sm border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100">
<option value="">All Styles</option>
@if(isset($portfolioStats['design_styles']))
@foreach($portfolioStats['design_styles'] as $style => $count)
<option value="{{ $style }}">{{ ucfirst($style) }} ({{ $count }})</option>
@endforeach
@endif
</select>
</div>
{{-- Founded Year Range --}}
<div class="grid grid-cols-2 gap-2">
<div>
<label class="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">From Year</label>
<input
type="number"
wire:model.live="foundedYearFrom"
placeholder="1900"
class="w-full text-sm border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"
>
</div>
<div>
<label class="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">To Year</label>
<input
type="number"
wire:model.live="foundedYearTo"
placeholder="{{ date('Y') }}"
class="w-full text-sm border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"
>
</div>
</div>
{{-- Innovation Score Range --}}
<div class="grid grid-cols-2 gap-2">
<div>
<label class="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">Min Innovation</label>
<input
type="number"
wire:model.live="minInnovationScore"
placeholder="0"
step="0.1"
min="0"
max="10"
class="w-full text-sm border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"
>
</div>
<div>
<label class="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">Max Innovation</label>
<input
type="number"
wire:model.live="maxInnovationScore"
placeholder="10"
step="0.1"
min="0"
max="10"
class="w-full text-sm border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"
>
</div>
</div>
{{-- Active Years Range --}}
<div class="grid grid-cols-2 gap-2">
<div>
<label class="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">Min Active Years</label>
<input
type="number"
wire:model.live="minActiveYears"
placeholder="0"
class="w-full text-sm border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"
>
</div>
<div>
<label class="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">Max Active Years</label>
<input
type="number"
wire:model.live="maxActiveYears"
placeholder=""
class="w-full text-sm border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"
>
</div>
</div>
</div>
</div>
{{-- Innovation Timeline Panel --}}
<div class="bg-purple-50 dark:bg-purple-900/20 rounded-lg p-4 mb-6">
<h3 class="text-sm font-medium text-purple-900 dark:text-purple-100 mb-3">Innovation Timeline</h3>
<div class="space-y-2 text-sm">
@if(isset($innovationTimeline['innovation_milestones']))
@foreach(array_slice($innovationTimeline['innovation_milestones'], 0, 3) as $milestone)
<div class="flex justify-between">
<span class="text-purple-700 dark:text-purple-300 truncate">{{ $milestone['name'] }}</span>
<span class="font-medium text-purple-900 dark:text-purple-100">{{ number_format($milestone['innovation_score'], 1) }}</span>
</div>
@endforeach
@endif
</div>
</div>
{{-- Portfolio Statistics Panel --}}
<div class="bg-pink-50 dark:bg-pink-900/20 rounded-lg p-4">
<h3 class="text-sm font-medium text-pink-900 dark:text-pink-100 mb-3">Portfolio Stats</h3>
<div class="space-y-2 text-sm">
<div class="flex justify-between">
<span class="text-pink-700 dark:text-pink-300">Total Designs</span>
<span class="font-medium text-pink-900 dark:text-pink-100">{{ $portfolioStats['total_designs'] ?? 0 }}</span>
</div>
<div class="flex justify-between">
<span class="text-pink-700 dark:text-pink-300">Avg Innovation</span>
<span class="font-medium text-pink-900 dark:text-pink-100">{{ number_format($portfolioStats['average_innovation_score'] ?? 0, 1) }}</span>
</div>
@if(isset($collaborationNetworks['network_hubs']))
<div class="flex justify-between">
<span class="text-pink-700 dark:text-pink-300">Network Hubs</span>
<span class="font-medium text-pink-900 dark:text-pink-100">{{ count($collaborationNetworks['network_hubs']) }}</span>
</div>
@endif
</div>
</div>
</x-slot>
{{-- Custom Mobile Specialty Filter Buttons --}}
<x-slot name="mobile-filters">
<div class="flex flex-wrap gap-2 mb-4">
<button
wire:click="toggleSpecialtyFilter('roller_coaster')"
class="px-3 py-1.5 text-sm rounded-full border transition-colors {{ in_array('roller_coaster', $specialties) ? 'bg-purple-500 text-white border-purple-500' : 'bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 border-gray-300 dark:border-gray-600' }}"
>
Coasters
@if(isset($portfolioStats['coaster_designers']))
<span class="ml-1 text-xs opacity-75">({{ $portfolioStats['coaster_designers'] }})</span>
@endif
</button>
<button
wire:click="toggleSpecialtyFilter('dark_ride')"
class="px-3 py-1.5 text-sm rounded-full border transition-colors {{ in_array('dark_ride', $specialties) ? 'bg-indigo-500 text-white border-indigo-500' : 'bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 border-gray-300 dark:border-gray-600' }}"
>
Dark Rides
@if(isset($portfolioStats['dark_ride_designers']))
<span class="ml-1 text-xs opacity-75">({{ $portfolioStats['dark_ride_designers'] }})</span>
@endif
</button>
<button
wire:click="toggleSpecialtyFilter('themed_experience')"
class="px-3 py-1.5 text-sm rounded-full border transition-colors {{ in_array('themed_experience', $specialties) ? 'bg-pink-500 text-white border-pink-500' : 'bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 border-gray-300 dark:border-gray-600' }}"
>
Experiences
@if(isset($portfolioStats['themed_experience_designers']))
<span class="ml-1 text-xs opacity-75">({{ $portfolioStats['themed_experience_designers'] }})</span>
@endif
</button>
</div>
</x-slot>
{{-- Custom Sort Options --}}
<x-slot name="sort-options">
<option value="name">Name</option>
<option value="founded_year">Founded Year</option>
<option value="innovation_score">Innovation Score</option>
<option value="designed_rides_count">Designs Count</option>
<option value="active_years">Active Years</option>
</x-slot>
{{-- Custom View Mode Options --}}
<x-slot name="view-modes">
<button
wire:click="setViewMode('grid')"
class="px-3 py-1 text-sm {{ $viewMode === 'grid' ? 'bg-purple-500 text-white' : 'bg-white dark:bg-gray-700 text-gray-700 dark:text-gray-300' }} rounded-l-md border border-gray-300 dark:border-gray-600"
>
Grid
</button>
<button
wire:click="setViewMode('portfolio')"
class="px-3 py-1 text-sm {{ $viewMode === 'portfolio' ? 'bg-purple-500 text-white' : 'bg-white dark:bg-gray-700 text-gray-700 dark:text-gray-300' }} rounded-r-md border-t border-r border-b border-gray-300 dark:border-gray-600"
>
Portfolio
</button>
</x-slot>
{{-- Custom Card Content for Grid View --}}
<x-slot name="card-content" :item="$designer">
{{-- Designer Header --}}
<div class="flex items-start justify-between mb-4">
<div class="flex-1">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">
{{ $designer->name }}
</h3>
@if($designer->headquarters)
<p class="text-sm text-gray-600 dark:text-gray-400">
{{ $designer->headquarters }}
</p>
@endif
</div>
@if($designer->innovation_score)
<div class="text-right">
<div class="text-lg font-bold text-purple-600 dark:text-purple-400">
{{ number_format($designer->innovation_score, 1) }}
</div>
<div class="text-xs text-gray-500">Innovation Score</div>
</div>
@endif
</div>
{{-- Specialty Badge --}}
<div class="flex flex-wrap gap-2 mb-4">
@if($designer->specialty)
<span class="px-3 py-1 text-sm bg-purple-100 dark:bg-purple-900 text-purple-800 dark:text-purple-200 rounded-full">
{{ ucfirst(str_replace('_', ' ', $designer->specialty)) }}
</span>
@endif
@if($designer->rides_count > 0)
<span class="px-3 py-1 text-sm bg-pink-100 dark:bg-pink-900 text-pink-800 dark:text-pink-200 rounded-full">
{{ $designer->rides_count }} Designs
</span>
@endif
</div>
{{-- Key Metrics --}}
<div class="grid grid-cols-2 gap-4 text-sm">
@if($designer->founded_year)
<div>
<div class="font-semibold text-gray-900 dark:text-gray-100">{{ $designer->founded_year }}</div>
<div class="text-gray-600 dark:text-gray-400">Founded</div>
</div>
@endif
@if($designer->active_years)
<div>
<div class="font-semibold text-gray-900 dark:text-gray-100">{{ $designer->active_years }}</div>
<div class="text-gray-600 dark:text-gray-400">Active Years</div>
</div>
@endif
@if($designer->design_style)
<div>
<div class="font-semibold text-gray-900 dark:text-gray-100">{{ ucfirst($designer->design_style) }}</div>
<div class="text-gray-600 dark:text-gray-400">Style</div>
</div>
@endif
@if($designer->rides_count)
<div>
<div class="font-semibold text-gray-900 dark:text-gray-100">{{ $designer->rides_count }}</div>
<div class="text-gray-600 dark:text-gray-400">Designs</div>
</div>
@endif
</div>
</x-slot>
{{-- Custom Portfolio View Content --}}
<x-slot name="portfolio-content" :item="$designer">
<div class="flex items-start justify-between mb-4">
<div class="flex-1">
<h3 class="text-xl font-semibold text-gray-900 dark:text-gray-100 mb-2">
{{ $designer->name }}
</h3>
@if($designer->description)
<p class="text-gray-600 dark:text-gray-400 mb-3">{{ $designer->description }}</p>
@endif
<div class="flex flex-wrap gap-2">
@if($designer->specialty)
<span class="px-3 py-1 text-sm bg-purple-100 dark:bg-purple-900 text-purple-800 dark:text-purple-200 rounded-full">
Specialty: {{ ucfirst(str_replace('_', ' ', $designer->specialty)) }}
</span>
@endif
@if($designer->design_style)
<span class="px-3 py-1 text-sm bg-pink-100 dark:bg-pink-900 text-pink-800 dark:text-pink-200 rounded-full">
Style: {{ ucfirst($designer->design_style) }}
</span>
@endif
@if($designer->rides_count > 0)
<span class="px-3 py-1 text-sm bg-indigo-100 dark:bg-indigo-900 text-indigo-800 dark:text-indigo-200 rounded-full">
{{ $designer->rides_count }} designs
</span>
@endif
</div>
</div>
@if($designer->innovation_score)
<div class="text-right ml-6">
<div class="text-2xl font-bold text-purple-600 dark:text-purple-400">
{{ number_format($designer->innovation_score, 1) }}
</div>
<div class="text-sm text-gray-500">Innovation Score</div>
</div>
@endif
</div>
<div class="grid grid-cols-4 gap-6 text-sm">
@if($designer->founded_year)
<div>
<div class="text-lg font-semibold text-gray-900 dark:text-gray-100">{{ $designer->founded_year }}</div>
<div class="text-gray-600 dark:text-gray-400">Founded</div>
</div>
@endif
@if($designer->active_years)
<div>
<div class="text-lg font-semibold text-gray-900 dark:text-gray-100">{{ $designer->active_years }}</div>
<div class="text-gray-600 dark:text-gray-400">Active Years</div>
</div>
@endif
@if($designer->rides_count)
<div>
<div class="text-lg font-semibold text-gray-900 dark:text-gray-100">{{ $designer->rides_count }}</div>
<div class="text-gray-600 dark:text-gray-400">Total Designs</div>
</div>
@endif
@if($designer->headquarters)
<div>
<div class="text-lg font-semibold text-gray-900 dark:text-gray-100">{{ $designer->headquarters }}</div>
<div class="text-gray-600 dark:text-gray-400">Headquarters</div>
</div>
@endif
</div>
</x-slot>
{{-- Custom Empty State --}}
<x-slot name="empty-state">
<div class="text-center py-12">
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"></path>
</svg>
<h3 class="mt-2 text-sm font-medium text-gray-900 dark:text-gray-100">No designers found</h3>
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">Try adjusting your search or filters.</p>
<div class="mt-6">
<button
wire:click="clearFilters"
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-purple-700 bg-purple-100 hover:bg-purple-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500"
>
Clear all filters
</button>
</div>
</div>
</x-slot>
{{-- Custom Clear Filters Action --}}
<x-slot name="clear-filters">
<button
wire:click="clearFilters"
class="text-sm text-purple-600 hover:text-purple-800 dark:text-purple-400 dark:hover:text-purple-200"
>
Clear all filters
</button>
</x-slot>
</x-universal-listing>
</div>

View File

@@ -0,0 +1,28 @@
<div>
{{-- Universal Listing System Integration --}}
<x-universal-listing
:entity-type="$entityType"
:items="$manufacturers"
:has-active-filters="$hasActiveFilters"
:view-mode="$viewMode"
:sort-by="$sortBy"
:sort-direction="$sortDirection"
:search="$search"
:per-page="$perPage"
:specializations="$specializations"
:total-rides-range="$totalRidesRange"
:industry-presence-range="$industryPresenceRange"
:founded-year-range="$foundedYearRange"
:active-only="$activeOnly"
:innovation-leaders-only="$innovationLeadersOnly"
wire:model.live="search"
wire:model.live="specializations"
wire:model.live="totalRidesRange"
wire:model.live="industryPresenceRange"
wire:model.live="foundedYearRange"
wire:model.live="activeOnly"
wire:model.live="innovationLeadersOnly"
wire:model.live="sortBy"
wire:model.live="viewMode"
/>
</div>

View File

@@ -0,0 +1,31 @@
{{-- ThrillWiki Reusable Component: OperatorHierarchyView --}}
<div class="thrillwiki-component"
x-data="{ loading: false }"
wire:loading.class="opacity-50">
{{-- Component Header --}}
<div class="component-header mb-4">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">
OperatorHierarchyView
</h3>
</div>
{{-- Component Content --}}
<div class="component-content">
<p class="text-gray-600 dark:text-gray-400">
ThrillWiki component content goes here.
</p>
{{-- Example interactive element --}}
<button wire:click="$refresh"
class="mt-4 px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors">
Refresh Component
</button>
</div>
{{-- Loading State --}}
<div wire:loading wire:target="$refresh"
class="absolute inset-0 bg-white bg-opacity-75 flex items-center justify-center">
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
</div>
</div>

View File

@@ -0,0 +1,10 @@
{{-- ThrillWiki Component: OperatorParksListing --}}
<div class="thrillwiki-component">
<h3 class="text-lg font-semibold mb-4 text-gray-900 dark:text-white">
OperatorParksListing
</h3>
<p class="text-gray-600 dark:text-gray-400">
ThrillWiki component content goes here.
</p>
</div>

View File

@@ -0,0 +1,31 @@
{{-- ThrillWiki Reusable Component: OperatorPortfolioCard --}}
<div class="thrillwiki-component"
x-data="{ loading: false }"
wire:loading.class="opacity-50">
{{-- Component Header --}}
<div class="component-header mb-4">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">
OperatorPortfolioCard
</h3>
</div>
{{-- Component Content --}}
<div class="component-content">
<p class="text-gray-600 dark:text-gray-400">
ThrillWiki component content goes here.
</p>
{{-- Example interactive element --}}
<button wire:click="$refresh"
class="mt-4 px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors">
Refresh Component
</button>
</div>
{{-- Loading State --}}
<div wire:loading wire:target="$refresh"
class="absolute inset-0 bg-white bg-opacity-75 flex items-center justify-center">
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
</div>
</div>

View File

@@ -0,0 +1,31 @@
{{-- ThrillWiki Reusable Component: OperatorsIndustryStats --}}
<div class="thrillwiki-component"
x-data="{ loading: false }"
wire:loading.class="opacity-50">
{{-- Component Header --}}
<div class="component-header mb-4">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">
OperatorsIndustryStats
</h3>
</div>
{{-- Component Content --}}
<div class="component-content">
<p class="text-gray-600 dark:text-gray-400">
ThrillWiki component content goes here.
</p>
{{-- Example interactive element --}}
<button wire:click="$refresh"
class="mt-4 px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors">
Refresh Component
</button>
</div>
{{-- Loading State --}}
<div wire:loading wire:target="$refresh"
class="absolute inset-0 bg-white bg-opacity-75 flex items-center justify-center">
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
</div>
</div>

View File

@@ -0,0 +1,419 @@
<div>
{{-- Universal Listing System Integration --}}
<x-universal-listing
:entity-type="$entityType"
:items="$operators"
:search="$search"
:sort-by="$sortBy"
:sort-direction="$sortDirection"
:view-mode="$viewMode"
:per-page="$perPage"
>
{{-- Custom Industry Statistics Header --}}
<x-slot name="header">
<div class="bg-gradient-to-r from-blue-500 to-purple-600 text-white p-6 rounded-lg mb-6">
<div class="text-center">
<h2 class="text-2xl font-bold mb-4">Industry Overview</h2>
<div class="grid grid-cols-2 md:grid-cols-4 gap-6">
<div class="text-center">
<div class="text-3xl font-bold">{{ $industryStats['total_operators'] ?? 0 }}</div>
<div class="text-sm opacity-90">Total Operators</div>
</div>
<div class="text-center">
<div class="text-3xl font-bold">{{ $industryStats['park_operators'] ?? 0 }}</div>
<div class="text-sm opacity-90">Park Operators</div>
</div>
<div class="text-center">
<div class="text-3xl font-bold">{{ $industryStats['manufacturers'] ?? 0 }}</div>
<div class="text-sm opacity-90">Manufacturers</div>
</div>
<div class="text-center">
<div class="text-3xl font-bold">{{ $industryStats['mixed_role'] ?? 0 }}</div>
<div class="text-sm opacity-90">Multi-Role</div>
</div>
</div>
</div>
</div>
</x-slot>
{{-- Custom Search Placeholder --}}
<x-slot name="search-placeholder">
Search operators, manufacturers, designers...
</x-slot>
{{-- Custom Filters Sidebar --}}
<x-slot name="filters">
{{-- Role Filters --}}
<div class="mb-6">
<h3 class="text-sm font-medium text-gray-900 dark:text-gray-100 mb-3">Operator Roles</h3>
<div class="space-y-2">
<label class="flex items-center">
<input
type="checkbox"
wire:model.live="roleFilter"
value="park_operator"
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
>
<span class="ml-2 text-sm text-gray-700 dark:text-gray-300">
Park Operators ({{ $industryStats['park_operators'] ?? 0 }})
</span>
</label>
<label class="flex items-center">
<input
type="checkbox"
wire:model.live="roleFilter"
value="ride_manufacturer"
class="rounded border-gray-300 text-green-600 focus:ring-green-500"
>
<span class="ml-2 text-sm text-gray-700 dark:text-gray-300">
Manufacturers ({{ $industryStats['manufacturers'] ?? 0 }})
</span>
</label>
<label class="flex items-center">
<input
type="checkbox"
wire:model.live="roleFilter"
value="ride_designer"
class="rounded border-gray-300 text-purple-600 focus:ring-purple-500"
>
<span class="ml-2 text-sm text-gray-700 dark:text-gray-300">
Designers ({{ $industryStats['designers'] ?? 0 }})
</span>
</label>
</div>
</div>
{{-- Industry Filters --}}
<div class="mb-6">
<h3 class="text-sm font-medium text-gray-900 dark:text-gray-100 mb-3">Industry Filters</h3>
<div class="space-y-4">
{{-- Company Size --}}
<div>
<label class="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">Company Size</label>
<select wire:model.live="companySize" class="w-full text-sm border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100">
<option value="">All Sizes</option>
<option value="small">Small (1-100)</option>
<option value="medium">Medium (101-1000)</option>
<option value="large">Large (1001-10000)</option>
<option value="enterprise">Enterprise (10000+)</option>
</select>
</div>
{{-- Industry Sector --}}
<div>
<label class="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">Industry Sector</label>
<select wire:model.live="industrySector" class="w-full text-sm border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100">
<option value="">All Sectors</option>
@if(isset($industryStats['sectors']))
@foreach($industryStats['sectors'] as $sector => $count)
<option value="{{ $sector }}">{{ ucfirst($sector) }} ({{ $count }})</option>
@endforeach
@endif
</select>
</div>
{{-- Founded Year Range --}}
<div class="grid grid-cols-2 gap-2">
<div>
<label class="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">From Year</label>
<input
type="number"
wire:model.live="foundedYearFrom"
placeholder="1900"
class="w-full text-sm border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"
>
</div>
<div>
<label class="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">To Year</label>
<input
type="number"
wire:model.live="foundedYearTo"
placeholder="{{ date('Y') }}"
class="w-full text-sm border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"
>
</div>
</div>
{{-- Geographic Presence --}}
<div>
<label class="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">Geographic Presence</label>
<select wire:model.live="geographicPresence" class="w-full text-sm border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100">
<option value="">All Levels</option>
<option value="regional">Regional</option>
<option value="international">International</option>
</select>
</div>
{{-- Revenue Range --}}
<div class="grid grid-cols-2 gap-2">
<div>
<label class="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">Min Revenue</label>
<input
type="number"
wire:model.live="minRevenue"
placeholder="0"
class="w-full text-sm border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"
>
</div>
<div>
<label class="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">Max Revenue</label>
<input
type="number"
wire:model.live="maxRevenue"
placeholder=""
class="w-full text-sm border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"
>
</div>
</div>
</div>
</div>
{{-- Industry Statistics Panel --}}
<div class="bg-blue-50 dark:bg-blue-900/20 rounded-lg p-4">
<h3 class="text-sm font-medium text-blue-900 dark:text-blue-100 mb-3">Industry Stats</h3>
<div class="space-y-2 text-sm">
<div class="flex justify-between">
<span class="text-blue-700 dark:text-blue-300">Total Operators</span>
<span class="font-medium text-blue-900 dark:text-blue-100">{{ $industryStats['total_operators'] ?? 0 }}</span>
</div>
<div class="flex justify-between">
<span class="text-blue-700 dark:text-blue-300">Multi-Role</span>
<span class="font-medium text-blue-900 dark:text-blue-100">{{ $industryStats['mixed_role'] ?? 0 }}</span>
</div>
@if(isset($marketData['total_market_cap']))
<div class="flex justify-between">
<span class="text-blue-700 dark:text-blue-300">Market Cap</span>
<span class="font-medium text-blue-900 dark:text-blue-100">${{ number_format($marketData['total_market_cap'] / 1000000000, 1) }}B</span>
</div>
@endif
</div>
</div>
</x-slot>
{{-- Custom Mobile Role Filter Buttons --}}
<x-slot name="mobile-filters">
<div class="flex flex-wrap gap-2 mb-4">
<button
wire:click="toggleRoleFilter('park_operator')"
class="px-3 py-1.5 text-sm rounded-full border transition-colors {{ in_array('park_operator', $roleFilter) ? 'bg-blue-500 text-white border-blue-500' : 'bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 border-gray-300 dark:border-gray-600' }}"
>
Operators
@if(isset($industryStats['park_operators']))
<span class="ml-1 text-xs opacity-75">({{ $industryStats['park_operators'] }})</span>
@endif
</button>
<button
wire:click="toggleRoleFilter('ride_manufacturer')"
class="px-3 py-1.5 text-sm rounded-full border transition-colors {{ in_array('ride_manufacturer', $roleFilter) ? 'bg-green-500 text-white border-green-500' : 'bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 border-gray-300 dark:border-gray-600' }}"
>
Manufacturers
@if(isset($industryStats['manufacturers']))
<span class="ml-1 text-xs opacity-75">({{ $industryStats['manufacturers'] }})</span>
@endif
</button>
<button
wire:click="toggleRoleFilter('ride_designer')"
class="px-3 py-1.5 text-sm rounded-full border transition-colors {{ in_array('ride_designer', $roleFilter) ? 'bg-purple-500 text-white border-purple-500' : 'bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 border-gray-300 dark:border-gray-600' }}"
>
Designers
@if(isset($industryStats['designers']))
<span class="ml-1 text-xs opacity-75">({{ $industryStats['designers'] }})</span>
@endif
</button>
</div>
</x-slot>
{{-- Custom Sort Options --}}
<x-slot name="sort-options">
<option value="name">Name</option>
<option value="founded_year">Founded Year</option>
<option value="parks_count">Parks Count</option>
<option value="rides_count">Rides Count</option>
<option value="revenue">Revenue</option>
<option value="market_influence">Market Influence</option>
</x-slot>
{{-- Custom View Mode Options --}}
<x-slot name="view-modes">
<button
wire:click="setViewMode('grid')"
class="px-3 py-1 text-sm {{ $viewMode === 'grid' ? 'bg-blue-500 text-white' : 'bg-white dark:bg-gray-700 text-gray-700 dark:text-gray-300' }} rounded-l-md border border-gray-300 dark:border-gray-600"
>
Grid
</button>
<button
wire:click="setViewMode('portfolio')"
class="px-3 py-1 text-sm {{ $viewMode === 'portfolio' ? 'bg-blue-500 text-white' : 'bg-white dark:bg-gray-700 text-gray-700 dark:text-gray-300' }} rounded-r-md border-t border-r border-b border-gray-300 dark:border-gray-600"
>
Portfolio
</button>
</x-slot>
{{-- Custom Card Content for Grid View --}}
<x-slot name="card-content" :item="$operator">
{{-- Operator Header --}}
<div class="flex items-start justify-between mb-4">
<div class="flex-1">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">
{{ $operator->name }}
</h3>
@if($operator->location)
<p class="text-sm text-gray-600 dark:text-gray-400">
{{ $operator->location->city }}, {{ $operator->location->country }}
</p>
@endif
</div>
@if($operator->market_influence_score)
<div class="text-right">
<div class="text-lg font-bold text-blue-600 dark:text-blue-400">
{{ number_format($operator->market_influence_score, 1) }}
</div>
<div class="text-xs text-gray-500">Influence Score</div>
</div>
@endif
</div>
{{-- Role Badges --}}
<div class="flex flex-wrap gap-2 mb-4">
@if($operator->parks_count > 0)
<span class="px-3 py-1 text-sm bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200 rounded-full">
{{ $operator->parks_count }} Parks
</span>
@endif
@if($operator->manufactured_rides_count > 0)
<span class="px-3 py-1 text-sm bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200 rounded-full">
{{ $operator->manufactured_rides_count }} Manufactured
</span>
@endif
@if($operator->designed_rides_count > 0)
<span class="px-3 py-1 text-sm bg-purple-100 dark:bg-purple-900 text-purple-800 dark:text-purple-200 rounded-full">
{{ $operator->designed_rides_count }} Designed
</span>
@endif
</div>
{{-- Key Metrics --}}
<div class="grid grid-cols-2 gap-4 text-sm">
@if($operator->founded_year)
<div>
<div class="font-semibold text-gray-900 dark:text-gray-100">{{ $operator->founded_year }}</div>
<div class="text-gray-600 dark:text-gray-400">Founded</div>
</div>
@endif
@if($operator->industry_sector)
<div>
<div class="font-semibold text-gray-900 dark:text-gray-100">{{ ucfirst($operator->industry_sector) }}</div>
<div class="text-gray-600 dark:text-gray-400">Sector</div>
</div>
@endif
@if($operator->employee_count)
<div>
<div class="font-semibold text-gray-900 dark:text-gray-100">{{ number_format($operator->employee_count) }}</div>
<div class="text-gray-600 dark:text-gray-400">Employees</div>
</div>
@endif
@if($operator->geographic_presence_level)
<div>
<div class="font-semibold text-gray-900 dark:text-gray-100">{{ ucfirst($operator->geographic_presence_level) }}</div>
<div class="text-gray-600 dark:text-gray-400">Presence</div>
</div>
@endif
</div>
</x-slot>
{{-- Custom Portfolio View Content --}}
<x-slot name="portfolio-content" :item="$operator">
<div class="flex items-start justify-between mb-4">
<div class="flex-1">
<h3 class="text-xl font-semibold text-gray-900 dark:text-gray-100 mb-2">
{{ $operator->name }}
</h3>
@if($operator->description)
<p class="text-gray-600 dark:text-gray-400 mb-3">{{ $operator->description }}</p>
@endif
<div class="flex flex-wrap gap-2">
@if($operator->parks_count > 0)
<span class="px-3 py-1 text-sm bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200 rounded-full">
Park Operator: {{ $operator->parks_count }} parks
</span>
@endif
@if($operator->manufactured_rides_count > 0)
<span class="px-3 py-1 text-sm bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200 rounded-full">
Manufacturer: {{ $operator->manufactured_rides_count }} rides
</span>
@endif
@if($operator->designed_rides_count > 0)
<span class="px-3 py-1 text-sm bg-purple-100 dark:bg-purple-900 text-purple-800 dark:text-purple-200 rounded-full">
Designer: {{ $operator->designed_rides_count }} rides
</span>
@endif
</div>
</div>
@if($operator->market_influence_score)
<div class="text-right ml-6">
<div class="text-2xl font-bold text-blue-600 dark:text-blue-400">
{{ number_format($operator->market_influence_score, 1) }}
</div>
<div class="text-sm text-gray-500">Market Influence</div>
</div>
@endif
</div>
<div class="grid grid-cols-4 gap-6 text-sm">
@if($operator->founded_year)
<div>
<div class="text-lg font-semibold text-gray-900 dark:text-gray-100">{{ $operator->founded_year }}</div>
<div class="text-gray-600 dark:text-gray-400">Founded</div>
</div>
@endif
@if($operator->industry_sector)
<div>
<div class="text-lg font-semibold text-gray-900 dark:text-gray-100">{{ ucfirst($operator->industry_sector) }}</div>
<div class="text-gray-600 dark:text-gray-400">Industry</div>
</div>
@endif
@if($operator->employee_count)
<div>
<div class="text-lg font-semibold text-gray-900 dark:text-gray-100">{{ number_format($operator->employee_count) }}</div>
<div class="text-gray-600 dark:text-gray-400">Employees</div>
</div>
@endif
@if($operator->location)
<div>
<div class="text-lg font-semibold text-gray-900 dark:text-gray-100">{{ $operator->location->country }}</div>
<div class="text-gray-600 dark:text-gray-400">Headquarters</div>
</div>
@endif
</div>
</x-slot>
{{-- Custom Empty State --}}
<x-slot name="empty-state">
<div class="text-center py-12">
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"></path>
</svg>
<h3 class="mt-2 text-sm font-medium text-gray-900 dark:text-gray-100">No operators found</h3>
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">Try adjusting your search or filters.</p>
<div class="mt-6">
<button
wire:click="clearFilters"
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-blue-700 bg-blue-100 hover:bg-blue-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
>
Clear all filters
</button>
</div>
</div>
</x-slot>
{{-- Custom Clear Filters Action --}}
<x-slot name="clear-filters">
<button
wire:click="clearFilters"
class="text-sm text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-200"
>
Clear all filters
</button>
</x-slot>
</x-universal-listing>
</div>

View File

@@ -0,0 +1,503 @@
<div class="operators-listing-container">
{{-- Mobile Layout (320px - 767px) --}}
<div class="block md:hidden">
{{-- Mobile Header with Search --}}
<div class="sticky top-0 bg-white dark:bg-gray-900 z-20 p-4 border-b border-gray-200 dark:border-gray-700">
<div class="space-y-3">
{{-- Search Input --}}
<div class="relative">
<input
type="text"
wire:model.live.debounce.300ms="search"
placeholder="Search operators, manufacturers, designers..."
class="w-full pl-10 pr-4 py-3 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
>
<svg class="absolute left-3 top-3.5 h-5 w-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
</svg>
</div>
{{-- Role Filter Buttons --}}
<div class="flex flex-wrap gap-2">
<button
wire:click="toggleRoleFilter('park_operator')"
class="px-3 py-1.5 text-sm rounded-full border transition-colors {{ in_array('park_operator', $roleFilter) ? 'bg-blue-500 text-white border-blue-500' : 'bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 border-gray-300 dark:border-gray-600' }}"
>
Operators
@if(isset($industryStats['park_operators']))
<span class="ml-1 text-xs opacity-75">({{ $industryStats['park_operators'] }})</span>
@endif
</button>
<button
wire:click="toggleRoleFilter('ride_manufacturer')"
class="px-3 py-1.5 text-sm rounded-full border transition-colors {{ in_array('ride_manufacturer', $roleFilter) ? 'bg-green-500 text-white border-green-500' : 'bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 border-gray-300 dark:border-gray-600' }}"
>
Manufacturers
@if(isset($industryStats['manufacturers']))
<span class="ml-1 text-xs opacity-75">({{ $industryStats['manufacturers'] }})</span>
@endif
</button>
<button
wire:click="toggleRoleFilter('ride_designer')"
class="px-3 py-1.5 text-sm rounded-full border transition-colors {{ in_array('ride_designer', $roleFilter) ? 'bg-purple-500 text-white border-purple-500' : 'bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 border-gray-300 dark:border-gray-600' }}"
>
Designers
@if(isset($industryStats['designers']))
<span class="ml-1 text-xs opacity-75">({{ $industryStats['designers'] }})</span>
@endif
</button>
</div>
</div>
</div>
{{-- Industry Statistics Banner --}}
<div class="bg-gradient-to-r from-blue-500 to-purple-600 text-white p-4 m-4 rounded-lg">
<div class="text-center">
<h3 class="text-lg font-semibold mb-2">Industry Overview</h3>
<div class="grid grid-cols-2 gap-4 text-sm">
<div>
<div class="text-2xl font-bold">{{ $industryStats['total_operators'] ?? 0 }}</div>
<div class="opacity-90">Total Operators</div>
</div>
<div>
<div class="text-2xl font-bold">{{ $industryStats['mixed_role'] ?? 0 }}</div>
<div class="opacity-90">Multi-Role</div>
</div>
</div>
</div>
</div>
{{-- Operator Cards --}}
<div class="space-y-4 p-4">
@forelse($operators as $operator)
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-4 border border-gray-200 dark:border-gray-700">
{{-- Operator Header --}}
<div class="flex items-start justify-between mb-3">
<div class="flex-1">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">
{{ $operator->name }}
</h3>
@if($operator->location)
<p class="text-sm text-gray-600 dark:text-gray-400">
{{ $operator->location->city }}, {{ $operator->location->country }}
</p>
@endif
</div>
<div class="text-right">
@if($operator->market_influence_score)
<div class="text-sm font-medium text-blue-600 dark:text-blue-400">
{{ number_format($operator->market_influence_score, 1) }}/100
</div>
<div class="text-xs text-gray-500">Influence</div>
@endif
</div>
</div>
{{-- Role Badges --}}
<div class="flex flex-wrap gap-2 mb-3">
@if($operator->parks_count > 0)
<span class="px-2 py-1 text-xs bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200 rounded-full">
Operator ({{ $operator->parks_count }} parks)
</span>
@endif
@if($operator->manufactured_rides_count > 0)
<span class="px-2 py-1 text-xs bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200 rounded-full">
Manufacturer ({{ $operator->manufactured_rides_count }} rides)
</span>
@endif
@if($operator->designed_rides_count > 0)
<span class="px-2 py-1 text-xs bg-purple-100 dark:bg-purple-900 text-purple-800 dark:text-purple-200 rounded-full">
Designer ({{ $operator->designed_rides_count }} rides)
</span>
@endif
</div>
{{-- Key Metrics --}}
<div class="grid grid-cols-3 gap-4 text-center text-sm">
@if($operator->founded_year)
<div>
<div class="font-semibold text-gray-900 dark:text-gray-100">{{ $operator->founded_year }}</div>
<div class="text-gray-600 dark:text-gray-400">Founded</div>
</div>
@endif
@if($operator->industry_sector)
<div>
<div class="font-semibold text-gray-900 dark:text-gray-100">{{ ucfirst($operator->industry_sector) }}</div>
<div class="text-gray-600 dark:text-gray-400">Sector</div>
</div>
@endif
@if($operator->company_size_category)
<div>
<div class="font-semibold text-gray-900 dark:text-gray-100">{{ ucfirst($operator->company_size_category) }}</div>
<div class="text-gray-600 dark:text-gray-400">Size</div>
</div>
@endif
</div>
</div>
@empty
<div class="text-center py-12">
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"></path>
</svg>
<h3 class="mt-2 text-sm font-medium text-gray-900 dark:text-gray-100">No operators found</h3>
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">Try adjusting your search or filters.</p>
</div>
@endforelse
</div>
{{-- Mobile Pagination --}}
@if($operators->hasPages())
<div class="sticky bottom-0 bg-white dark:bg-gray-900 p-4 border-t border-gray-200 dark:border-gray-700">
{{ $operators->links('pagination.mobile') }}
</div>
@endif
</div>
{{-- Tablet Layout (768px - 1023px) --}}
<div class="hidden md:block lg:hidden">
<div class="flex h-screen">
{{-- Filter Sidebar --}}
<div class="w-80 bg-gray-50 dark:bg-gray-800 overflow-y-auto border-r border-gray-200 dark:border-gray-700">
<div class="p-6">
{{-- Search --}}
<div class="relative mb-6">
<input
type="text"
wire:model.live.debounce.300ms="search"
placeholder="Search operators..."
class="w-full pl-10 pr-4 py-3 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"
>
<svg class="absolute left-3 top-3.5 h-5 w-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
</svg>
</div>
{{-- Role Filters --}}
<div class="mb-6">
<h3 class="text-sm font-medium text-gray-900 dark:text-gray-100 mb-3">Operator Roles</h3>
<div class="space-y-2">
<label class="flex items-center">
<input
type="checkbox"
wire:model.live="roleFilter"
value="park_operator"
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
>
<span class="ml-2 text-sm text-gray-700 dark:text-gray-300">
Park Operators ({{ $industryStats['park_operators'] ?? 0 }})
</span>
</label>
<label class="flex items-center">
<input
type="checkbox"
wire:model.live="roleFilter"
value="ride_manufacturer"
class="rounded border-gray-300 text-green-600 focus:ring-green-500"
>
<span class="ml-2 text-sm text-gray-700 dark:text-gray-300">
Manufacturers ({{ $industryStats['manufacturers'] ?? 0 }})
</span>
</label>
<label class="flex items-center">
<input
type="checkbox"
wire:model.live="roleFilter"
value="ride_designer"
class="rounded border-gray-300 text-purple-600 focus:ring-purple-500"
>
<span class="ml-2 text-sm text-gray-700 dark:text-gray-300">
Designers ({{ $industryStats['designers'] ?? 0 }})
</span>
</label>
</div>
</div>
{{-- Industry Filters --}}
<div class="mb-6">
<h3 class="text-sm font-medium text-gray-900 dark:text-gray-100 mb-3">Industry Filters</h3>
<div class="space-y-4">
{{-- Company Size --}}
<div>
<label class="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">Company Size</label>
<select wire:model.live="companySize" class="w-full text-sm border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100">
<option value="">All Sizes</option>
<option value="small">Small (1-100)</option>
<option value="medium">Medium (101-1000)</option>
<option value="large">Large (1001-10000)</option>
<option value="enterprise">Enterprise (10000+)</option>
</select>
</div>
{{-- Industry Sector --}}
<div>
<label class="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">Industry Sector</label>
<select wire:model.live="industrySector" class="w-full text-sm border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100">
<option value="">All Sectors</option>
@if(isset($industryStats['sectors']))
@foreach($industryStats['sectors'] as $sector => $count)
<option value="{{ $sector }}">{{ ucfirst($sector) }} ({{ $count }})</option>
@endforeach
@endif
</select>
</div>
{{-- Founded Year Range --}}
<div class="grid grid-cols-2 gap-2">
<div>
<label class="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">From Year</label>
<input
type="number"
wire:model.live="foundedYearFrom"
placeholder="1900"
class="w-full text-sm border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"
>
</div>
<div>
<label class="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">To Year</label>
<input
type="number"
wire:model.live="foundedYearTo"
placeholder="{{ date('Y') }}"
class="w-full text-sm border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"
>
</div>
</div>
</div>
</div>
{{-- Industry Statistics --}}
<div class="bg-blue-50 dark:bg-blue-900/20 rounded-lg p-4">
<h3 class="text-sm font-medium text-blue-900 dark:text-blue-100 mb-3">Industry Stats</h3>
<div class="space-y-2 text-sm">
<div class="flex justify-between">
<span class="text-blue-700 dark:text-blue-300">Total Operators</span>
<span class="font-medium text-blue-900 dark:text-blue-100">{{ $industryStats['total_operators'] ?? 0 }}</span>
</div>
<div class="flex justify-between">
<span class="text-blue-700 dark:text-blue-300">Multi-Role</span>
<span class="font-medium text-blue-900 dark:text-blue-100">{{ $industryStats['mixed_role'] ?? 0 }}</span>
</div>
</div>
</div>
</div>
</div>
{{-- Main Content --}}
<div class="flex-1 flex flex-col">
{{-- Header --}}
<div class="bg-white dark:bg-gray-900 p-6 border-b border-gray-200 dark:border-gray-700">
<div class="flex items-center justify-between">
<div>
<h1 class="text-xl font-semibold text-gray-900 dark:text-gray-100">
{{ $operators->total() }} Industry Operators
</h1>
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1">
Discover theme park operators, ride manufacturers, and designers
</p>
</div>
<div class="flex items-center space-x-4">
{{-- Sort Selector --}}
<select wire:model.live="sortBy" class="text-sm border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100">
<option value="name">Name</option>
<option value="founded_year">Founded Year</option>
<option value="parks_count">Parks Count</option>
<option value="rides_count">Rides Count</option>
<option value="market_influence">Market Influence</option>
</select>
{{-- View Toggle --}}
<div class="flex rounded-md border border-gray-300 dark:border-gray-600">
<button
wire:click="setViewMode('grid')"
class="px-3 py-1 text-sm {{ $viewMode === 'grid' ? 'bg-blue-500 text-white' : 'bg-white dark:bg-gray-700 text-gray-700 dark:text-gray-300' }} rounded-l-md"
>
Grid
</button>
<button
wire:click="setViewMode('portfolio')"
class="px-3 py-1 text-sm {{ $viewMode === 'portfolio' ? 'bg-blue-500 text-white' : 'bg-white dark:bg-gray-700 text-gray-700 dark:text-gray-300' }} rounded-r-md"
>
Portfolio
</button>
</div>
</div>
</div>
</div>
{{-- Content Grid --}}
<div class="flex-1 overflow-y-auto p-6">
@if($viewMode === 'grid')
<div class="grid grid-cols-2 gap-6">
@foreach($operators as $operator)
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 border border-gray-200 dark:border-gray-700">
{{-- Operator Header --}}
<div class="flex items-start justify-between mb-4">
<div class="flex-1">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">
{{ $operator->name }}
</h3>
@if($operator->location)
<p class="text-sm text-gray-600 dark:text-gray-400">
{{ $operator->location->city }}, {{ $operator->location->country }}
</p>
@endif
</div>
@if($operator->market_influence_score)
<div class="text-right">
<div class="text-lg font-bold text-blue-600 dark:text-blue-400">
{{ number_format($operator->market_influence_score, 1) }}
</div>
<div class="text-xs text-gray-500">Influence Score</div>
</div>
@endif
</div>
{{-- Role Badges --}}
<div class="flex flex-wrap gap-2 mb-4">
@if($operator->parks_count > 0)
<span class="px-3 py-1 text-sm bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200 rounded-full">
{{ $operator->parks_count }} Parks
</span>
@endif
@if($operator->manufactured_rides_count > 0)
<span class="px-3 py-1 text-sm bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200 rounded-full">
{{ $operator->manufactured_rides_count }} Manufactured
</span>
@endif
@if($operator->designed_rides_count > 0)
<span class="px-3 py-1 text-sm bg-purple-100 dark:bg-purple-900 text-purple-800 dark:text-purple-200 rounded-full">
{{ $operator->designed_rides_count }} Designed
</span>
@endif
</div>
{{-- Key Metrics --}}
<div class="grid grid-cols-2 gap-4 text-sm">
@if($operator->founded_year)
<div>
<div class="font-semibold text-gray-900 dark:text-gray-100">{{ $operator->founded_year }}</div>
<div class="text-gray-600 dark:text-gray-400">Founded</div>
</div>
@endif
@if($operator->industry_sector)
<div>
<div class="font-semibold text-gray-900 dark:text-gray-100">{{ ucfirst($operator->industry_sector) }}</div>
<div class="text-gray-600 dark:text-gray-400">Sector</div>
</div>
@endif
@if($operator->employee_count)
<div>
<div class="font-semibold text-gray-900 dark:text-gray-100">{{ number_format($operator->employee_count) }}</div>
<div class="text-gray-600 dark:text-gray-400">Employees</div>
</div>
@endif
@if($operator->geographic_presence_level)
<div>
<div class="font-semibold text-gray-900 dark:text-gray-100">{{ ucfirst($operator->geographic_presence_level) }}</div>
<div class="text-gray-600 dark:text-gray-400">Presence</div>
</div>
@endif
</div>
</div>
@endforeach
</div>
@else
{{-- Portfolio View --}}
<div class="space-y-6">
@foreach($operators as $operator)
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 border border-gray-200 dark:border-gray-700">
<div class="flex items-start justify-between mb-4">
<div class="flex-1">
<h3 class="text-xl font-semibold text-gray-900 dark:text-gray-100 mb-2">
{{ $operator->name }}
</h3>
@if($operator->description)
<p class="text-gray-600 dark:text-gray-400 mb-3">{{ $operator->description }}</p>
@endif
<div class="flex flex-wrap gap-2">
@if($operator->parks_count > 0)
<span class="px-3 py-1 text-sm bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200 rounded-full">
Park Operator: {{ $operator->parks_count }} parks
</span>
@endif
@if($operator->manufactured_rides_count > 0)
<span class="px-3 py-1 text-sm bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200 rounded-full">
Manufacturer: {{ $operator->manufactured_rides_count }} rides
</span>
@endif
@if($operator->designed_rides_count > 0)
<span class="px-3 py-1 text-sm bg-purple-100 dark:bg-purple-900 text-purple-800 dark:text-purple-200 rounded-full">
Designer: {{ $operator->designed_rides_count }} rides
</span>
@endif
</div>
</div>
@if($operator->market_influence_score)
<div class="text-right ml-6">
<div class="text-2xl font-bold text-blue-600 dark:text-blue-400">
{{ number_format($operator->market_influence_score, 1) }}
</div>
<div class="text-sm text-gray-500">Market Influence</div>
</div>
@endif
</div>
<div class="grid grid-cols-4 gap-6 text-sm">
@if($operator->founded_year)
<div>
<div class="text-lg font-semibold text-gray-900 dark:text-gray-100">{{ $operator->founded_year }}</div>
<div class="text-gray-600 dark:text-gray-400">Founded</div>
</div>
@endif
@if($operator->industry_sector)
<div>
<div class="text-lg font-semibold text-gray-900 dark:text-gray-100">{{ ucfirst($operator->industry_sector) }}</div>
<div class="text-gray-600 dark:text-gray-400">Industry</div>
</div>
@endif
@if($operator->employee_count)
<div>
<div class="text-lg font-semibold text-gray-900 dark:text-gray-100">{{ number_format($operator->employee_count) }}</div>
<div class="text-gray-600 dark:text-gray-400">Employees</div>
</div>
@endif
@if($operator->location)
<div>
<div class="text-lg font-semibold text-gray-900 dark:text-gray-100">{{ $operator->location->country }}</div>
<div class="text-gray-600 dark:text-gray-400">Headquarters</div>
</div>
@endif
</div>
</div>
@endforeach
</div>
@endif
{{-- Pagination --}}
@if($operators->hasPages())
<div class="mt-8">
{{ $operators->links() }}
</div>
@endif
</div>
</div>
</div>
</div>
{{-- Desktop Layout (1024px+) --}}
<div class="hidden lg:block">
<div class="flex h-screen">
{{-- Advanced Filter Sidebar --}}
<div class="w-80 bg-gray-50 dark:bg-gray-800 overflow-y-auto border-r border-gray-200 dark:border-gray-700">
<div class="p-6">
{{-- Search --}}
<div class="relative mb-6">
<input
type="text"
wire:model.live.debounce.300ms="search"
placeholder="Search operators, manufacturers, designers..."
class="w-full pl-10 pr-4 py-3 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"
>
<svg class="absolute left-3 top-3.5 h-5 w-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
</svg>

View File

@@ -0,0 +1,31 @@
{{-- ThrillWiki Reusable Component: OperatorsMarketAnalysis --}}
<div class="thrillwiki-component"
x-data="{ loading: false }"
wire:loading.class="opacity-50">
{{-- Component Header --}}
<div class="component-header mb-4">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">
OperatorsMarketAnalysis
</h3>
</div>
{{-- Component Content --}}
<div class="component-content">
<p class="text-gray-600 dark:text-gray-400">
ThrillWiki component content goes here.
</p>
{{-- Example interactive element --}}
<button wire:click="$refresh"
class="mt-4 px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors">
Refresh Component
</button>
</div>
{{-- Loading State --}}
<div wire:loading wire:target="$refresh"
class="absolute inset-0 bg-white bg-opacity-75 flex items-center justify-center">
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
</div>
</div>

View File

@@ -0,0 +1,31 @@
{{-- ThrillWiki Reusable Component: OperatorsRoleFilter --}}
<div class="thrillwiki-component"
x-data="{ loading: false }"
wire:loading.class="opacity-50">
{{-- Component Header --}}
<div class="component-header mb-4">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">
OperatorsRoleFilter
</h3>
</div>
{{-- Component Content --}}
<div class="component-content">
<p class="text-gray-600 dark:text-gray-400">
ThrillWiki component content goes here.
</p>
{{-- Example interactive element --}}
<button wire:click="$refresh"
class="mt-4 px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors">
Refresh Component
</button>
</div>
{{-- Loading State --}}
<div wire:loading wire:target="$refresh"
class="absolute inset-0 bg-white bg-opacity-75 flex items-center justify-center">
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
</div>
</div>

View File

@@ -1,10 +1,357 @@
{{-- ThrillWiki Component: ParkRidesListing --}}
<div class="thrillwiki-component">
<h3 class="text-lg font-semibold mb-4 text-gray-900 dark:text-white">
ParkRidesListing
</h3>
<p class="text-gray-600 dark:text-gray-400">
ThrillWiki component content goes here.
</p>
</div>
<div class="space-y-6">
<!-- Park Header with Stats -->
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-6">
<div class="flex flex-col lg:flex-row lg:items-center lg:justify-between space-y-4 lg:space-y-0">
<div>
<h1 class="text-2xl sm:text-3xl font-bold text-gray-900 dark:text-white">
{{ $park->name }} Rides
</h1>
<p class="mt-2 text-gray-600 dark:text-gray-400">
Explore all rides at {{ $park->name }}
</p>
</div>
<!-- Park Statistics -->
<div class="grid grid-cols-2 sm:grid-cols-4 gap-4 lg:gap-6">
<div class="text-center">
<div class="text-2xl font-bold text-blue-600 dark:text-blue-400">
{{ $parkStats['total_rides'] }}
</div>
<div class="text-sm text-gray-500 dark:text-gray-400">Total Rides</div>
</div>
<div class="text-center">
<div class="text-2xl font-bold text-green-600 dark:text-green-400">
{{ $parkStats['operating_rides'] }}
</div>
<div class="text-sm text-gray-500 dark:text-gray-400">Operating</div>
</div>
<div class="text-center">
<div class="text-2xl font-bold text-purple-600 dark:text-purple-400">
{{ $parkStats['categories'] }}
</div>
<div class="text-sm text-gray-500 dark:text-gray-400">Categories</div>
</div>
@if($parkStats['avg_rating'])
<div class="text-center">
<div class="text-2xl font-bold text-yellow-600 dark:text-yellow-400">
{{ $parkStats['avg_rating'] }}
</div>
<div class="text-sm text-gray-500 dark:text-gray-400">Avg Rating</div>
</div>
@endif
</div>
</div>
</div>
<!-- Search and Filter Controls -->
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700">
<!-- Search Bar -->
<div class="p-4 border-b border-gray-200 dark:border-gray-700">
<div class="flex flex-col sm:flex-row gap-4">
<div class="flex-1">
<div class="relative">
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<svg class="h-5 w-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
</svg>
</div>
<input
type="text"
wire:model.live.debounce.300ms="searchTerm"
placeholder="Search rides by name, description, manufacturer, or designer..."
class="block w-full pl-10 pr-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-500 dark:placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
>
</div>
</div>
<!-- Filter Toggle (Mobile) -->
<button
wire:click="toggleFilters"
class="sm:hidden flex items-center justify-center px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-600"
>
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.207A1 1 0 013 6.5V4z"></path>
</svg>
Filters
@if($activeFiltersCount > 0)
<span class="ml-2 inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200">
{{ $activeFiltersCount }}
</span>
@endif
</button>
</div>
</div>
<!-- Filters Section -->
<div class="transition-all duration-300 {{ $showFilters ? 'block' : 'hidden' }} sm:block">
<div class="p-4 space-y-4">
<!-- Category and Status Filters -->
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<!-- Categories -->
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Category
</label>
<div class="flex flex-wrap gap-2">
@foreach($categories as $category)
<button
wire:click="setCategory('{{ $category['value'] }}')"
class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium transition-colors
{{ $selectedCategory === $category['value']
? 'bg-blue-100 text-blue-800 border-blue-200 dark:bg-blue-900/20 dark:text-blue-300 dark:border-blue-700'
: 'bg-gray-100 text-gray-700 border-gray-200 hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-600 dark:hover:bg-gray-600' }} border"
>
{{ $category['label'] }}
<span class="ml-1 text-xs">({{ $category['count'] }})</span>
</button>
@endforeach
</div>
</div>
<!-- Statuses -->
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Status
</label>
<div class="flex flex-wrap gap-2">
@foreach($statuses as $status)
<button
wire:click="setStatus('{{ $status['value'] }}')"
class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium transition-colors
{{ $selectedStatus === $status['value']
? 'bg-green-100 text-green-800 border-green-200 dark:bg-green-900/20 dark:text-green-300 dark:border-green-700'
: 'bg-gray-100 text-gray-700 border-gray-200 hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-600 dark:hover:bg-gray-600' }} border"
>
{{ $status['label'] }}
<span class="ml-1 text-xs">({{ $status['count'] }})</span>
</button>
@endforeach
</div>
</div>
</div>
<!-- Sort and Actions -->
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 pt-4 border-t border-gray-200 dark:border-gray-700">
<!-- Sort Options -->
<div class="flex items-center space-x-4">
<label class="text-sm font-medium text-gray-700 dark:text-gray-300">Sort by:</label>
<select
wire:model.live="sortBy"
class="rounded-lg border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white text-sm focus:border-blue-500 focus:ring-blue-500"
>
@foreach($sortOptions as $value => $label)
<option value="{{ $value }}">{{ $label }}</option>
@endforeach
</select>
<button
wire:click="setSortBy('{{ $sortBy }}')"
class="p-1 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
title="Toggle sort direction"
>
<svg class="w-4 h-4 transform {{ $sortDirection === 'desc' ? 'rotate-180' : '' }}" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 15l7-7 7 7"></path>
</svg>
</button>
</div>
<!-- Clear Filters -->
@if($activeFiltersCount > 0)
<button
wire:click="clearFilters"
class="text-sm text-red-600 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 font-medium"
>
Clear all filters ({{ $activeFiltersCount }})
</button>
@endif
</div>
</div>
</div>
</div>
<!-- Results Count and Loading -->
<div class="flex items-center justify-between">
<div class="text-sm text-gray-600 dark:text-gray-400">
@if($rides->total() > 0)
Showing {{ $rides->firstItem() }}-{{ $rides->lastItem() }} of {{ $rides->total() }} rides
@else
No rides found
@endif
</div>
<div wire:loading class="flex items-center space-x-2 text-gray-600 dark:text-gray-400">
<svg class="animate-spin h-4 w-4" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
<span class="text-sm">Loading...</span>
</div>
</div>
<!-- Rides Grid -->
@if($rides->count() > 0)
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 gap-6">
@foreach($rides as $ride)
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden hover:shadow-md transition-shadow">
<!-- Ride Image -->
<div class="aspect-w-16 aspect-h-9 bg-gray-200 dark:bg-gray-700">
@if($ride->photos->count() > 0)
<img
src="{{ $ride->photos->first()->url }}"
alt="{{ $ride->name }}"
class="w-full h-48 object-cover"
loading="lazy"
>
@else
<div class="w-full h-48 flex items-center justify-center">
<svg class="w-12 h-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg>
</div>
@endif
</div>
<!-- Ride Info -->
<div class="p-4">
<div class="flex items-start justify-between">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white truncate">
<a href="{{ route('rides.show', $ride) }}" class="hover:text-blue-600 dark:hover:text-blue-400">
{{ $ride->name }}
</a>
</h3>
<!-- Status Badge -->
<span class="ml-2 inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium
{{ $ride->status === 'operating' ? 'bg-green-100 text-green-800 dark:bg-green-900/20 dark:text-green-300' : 'bg-red-100 text-red-800 dark:bg-red-900/20 dark:text-red-300' }}">
{{ ucfirst($ride->status) }}
</span>
</div>
<!-- Category -->
<div class="mt-1 text-sm text-gray-600 dark:text-gray-400">
{{ ucfirst(str_replace('_', ' ', $ride->category)) }}
</div>
<!-- Description -->
@if($ride->description)
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400 line-clamp-2">
{{ $ride->description }}
</p>
@endif
<!-- Ride Details -->
<div class="mt-3 space-y-1 text-xs text-gray-500 dark:text-gray-400">
@if($ride->opening_year)
<div>Opened: {{ $ride->opening_year }}</div>
@endif
@if($ride->height_requirement)
<div>Height: {{ $ride->height_requirement }}cm minimum</div>
@endif
@if($ride->manufacturer)
<div>Manufacturer: {{ $ride->manufacturer->name }}</div>
@endif
</div>
<!-- Rating -->
@if($ride->reviews_avg_rating)
<div class="mt-3 flex items-center">
<div class="flex items-center">
@for($i = 1; $i <= 5; $i++)
<svg class="w-4 h-4 {{ $i <= round($ride->reviews_avg_rating) ? 'text-yellow-400' : 'text-gray-300 dark:text-gray-600' }}" fill="currentColor" viewBox="0 0 20 20">
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"></path>
</svg>
@endfor
</div>
<span class="ml-1 text-sm text-gray-600 dark:text-gray-400">
{{ round($ride->reviews_avg_rating, 1) }} ({{ $ride->reviews_count }})
</span>
</div>
@endif
</div>
</div>
@endforeach
</div>
<!-- Pagination -->
<div class="mt-8">
{{ $rides->links() }}
</div>
@else
<!-- Empty State -->
<div class="text-center py-12">
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.172 16.172a4 4 0 015.656 0M9 12h6m-6-4h6m2 5.291A7.962 7.962 0 0118 12a8 8 0 01-8 8 8 8 0 01-8-8 8 8 0 018-8c2.152 0 4.139.851 5.582 2.236"></path>
</svg>
<h3 class="mt-2 text-sm font-medium text-gray-900 dark:text-white">No rides found</h3>
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
@if($activeFiltersCount > 0)
Try adjusting your search criteria or clearing filters.
@else
This park doesn't have any rides yet.
@endif
</p>
@if($activeFiltersCount > 0)
<div class="mt-6">
<button
wire:click="clearFilters"
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-blue-700 bg-blue-100 hover:bg-blue-200 dark:bg-blue-900/20 dark:text-blue-300 dark:hover:bg-blue-900/30"
>
Clear all filters
</button>
</div>
@endif
</div>
@endif
</div>
<!-- Screen-Agnostic Responsive Styles -->
<style>
/* Mobile-first responsive design */
@media (max-width: 320px) {
.grid-cols-1 { grid-template-columns: repeat(1, minmax(0, 1fr)); }
}
@media (min-width: 640px) {
.sm\:grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 1024px) {
.lg\:grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (min-width: 1280px) {
.xl\:grid-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
@media (min-width: 1536px) {
.2xl\:grid-cols-5 { grid-template-columns: repeat(5, minmax(0, 1fr)); }
}
@media (min-width: 1920px) {
.2xl\:grid-cols-5 { grid-template-columns: repeat(6, minmax(0, 1fr)); }
}
@media (min-width: 2560px) {
.2xl\:grid-cols-5 { grid-template-columns: repeat(8, minmax(0, 1fr)); }
}
/* Touch-friendly targets for mobile */
@media (max-width: 768px) {
button, select, input {
min-height: 44px;
}
}
/* Line clamp utility */
.line-clamp-2 {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
/* High DPI display optimizations */
@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
.border { border-width: 0.5px; }
}
</style>

View File

@@ -0,0 +1,31 @@
{{-- ThrillWiki Reusable Component: ParksFilters --}}
<div class="thrillwiki-component"
x-data="{ loading: false }"
wire:loading.class="opacity-50">
{{-- Component Header --}}
<div class="component-header mb-4">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">
ParksFilters
</h3>
</div>
{{-- Component Content --}}
<div class="component-content">
<p class="text-gray-600 dark:text-gray-400">
ThrillWiki component content goes here.
</p>
{{-- Example interactive element --}}
<button wire:click="$refresh"
class="mt-4 px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors">
Refresh Component
</button>
</div>
{{-- Loading State --}}
<div wire:loading wire:target="$refresh"
class="absolute inset-0 bg-white bg-opacity-75 flex items-center justify-center">
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
</div>
</div>

View File

@@ -0,0 +1,217 @@
<div>
{{-- Universal Listing Component Integration --}}
<x-universal-listing
:entity-type="$entityType"
:items="$parks"
:search="$search"
:sort-by="$sortBy"
:sort-direction="$sortDirection"
:show-filters="$showFilters"
:active-filters="$activeFilters"
:location-enabled="$locationEnabled"
:location-loading="$locationLoading"
:user-location="$userLocation"
wire:model.live="search"
>
{{-- Custom Location Controls Slot --}}
<x-slot name="locationControls">
@if(!$locationEnabled)
<button
wire:click="enableLocation"
wire:loading.attr="disabled"
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
>
<svg wire:loading.remove wire:target="enableLocation" class="w-4 h-4 mr-2" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M5.05 4.05a7 7 0 119.9 9.9L10 18.9l-4.95-4.95a7 7 0 010-9.9zM10 11a2 2 0 100-4 2 2 0 000 4z" clip-rule="evenodd"></path>
</svg>
<svg wire:loading wire:target="enableLocation" class="animate-spin w-4 h-4 mr-2" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="m4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
<span wire:loading.remove wire:target="enableLocation">Find Parks Near Me</span>
<span wire:loading wire:target="enableLocation">Getting Location...</span>
</button>
@endif
</x-slot>
{{-- Custom Filters Slot for Parks-Specific Filters --}}
<x-slot name="customFilters">
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
{{-- Operator Filter --}}
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Operator</label>
<select wire:model.live="operatorId" class="block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
<option value="">All Operators</option>
@foreach($operators as $operator)
<option value="{{ $operator->id }}">{{ $operator->name }}</option>
@endforeach
</select>
</div>
{{-- Region Filter --}}
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Region</label>
<select wire:model.live="region" class="block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
<option value="">All Regions</option>
@foreach($regions as $regionOption)
<option value="{{ $regionOption }}">{{ $regionOption }}</option>
@endforeach
</select>
</div>
{{-- Country Filter --}}
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Country</label>
<select wire:model.live="country" class="block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
<option value="">All Countries</option>
@foreach($countries as $countryOption)
<option value="{{ $countryOption }}">{{ $countryOption }}</option>
@endforeach
</select>
</div>
{{-- Park Type Filter --}}
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Park Type</label>
<select wire:model.live="parkType" class="block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
<option value="">All Types</option>
@foreach($parkTypes as $type)
<option value="{{ $type }}">{{ $type }}</option>
@endforeach
</select>
</div>
{{-- Opening Year Range --}}
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Opening Year</label>
<div class="grid grid-cols-2 gap-2">
<input wire:model.live="openingYearFrom" type="number" placeholder="From" min="1800" max="{{ date('Y') }}" class="block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
<input wire:model.live="openingYearTo" type="number" placeholder="To" min="1800" max="{{ date('Y') }}" class="block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
</div>
</div>
{{-- Area Range --}}
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Area (acres)</label>
<div class="grid grid-cols-2 gap-2">
<input wire:model.live="minArea" type="number" placeholder="Min" min="0" class="block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
<input wire:model.live="maxArea" type="number" placeholder="Max" min="0" class="block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
</div>
</div>
{{-- Minimum Rides --}}
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Minimum Rides</label>
<input wire:model.live="minRides" type="number" placeholder="Min rides" min="0" class="block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
</div>
{{-- Distance Filter (only if location enabled) --}}
@if($locationEnabled)
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Max Distance (km)</label>
<input wire:model.live="maxDistance" type="number" placeholder="Distance" min="1" class="block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
</div>
@endif
</div>
</x-slot>
{{-- Custom Sort Options Slot --}}
<x-slot name="customSortOptions">
@if($locationEnabled)
<button wire:click="sortBy('distance')" class="inline-flex items-center px-3 py-1 rounded-md text-sm {{ $sortBy === 'distance' ? 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200' : 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white' }}">
Distance
@if($sortBy === 'distance')
<svg class="ml-1 w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="{{ $sortDirection === 'asc' ? 'M14.707 12.707a1 1 0 01-1.414 0L10 9.414l-3.293 3.293a1 1 0 01-1.414-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 010 1.414z' : 'M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z' }}" clip-rule="evenodd"></path>
</svg>
@endif
</button>
@endif
<button wire:click="sortBy('rides_count')" class="inline-flex items-center px-3 py-1 rounded-md text-sm {{ $sortBy === 'rides_count' ? 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200' : 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white' }}">
Rides
@if($sortBy === 'rides_count')
<svg class="ml-1 w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="{{ $sortDirection === 'asc' ? 'M14.707 12.707a1 1 0 01-1.414 0L10 9.414l-3.293 3.293a1 1 0 01-1.414-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 010 1.414z' : 'M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z' }}" clip-rule="evenodd"></path>
</svg>
@endif
</button>
<button wire:click="sortBy('opening_date')" class="inline-flex items-center px-3 py-1 rounded-md text-sm {{ $sortBy === 'opening_date' ? 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200' : 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white' }}">
Opening Date
@if($sortBy === 'opening_date')
<svg class="ml-1 w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="{{ $sortDirection === 'asc' ? 'M14.707 12.707a1 1 0 01-1.414 0L10 9.414l-3.293 3.293a1 1 0 01-1.414-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 010 1.414z' : 'M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z' }}" clip-rule="evenodd"></path>
</svg>
@endif
</button>
</x-slot>
{{-- Custom Card Content for Parks --}}
<x-slot name="customCardContent" :item="null">
@foreach($parks as $park)
<x-universal-listing-card
:item="$park"
:entity-type="$entityType"
:location-enabled="$locationEnabled"
:user-location="$userLocation"
>
{{-- Custom Park-Specific Content --}}
<x-slot name="customContent">
@if($locationEnabled && isset($park->distance))
<div class="ml-2 flex-shrink-0">
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200">
{{ number_format($park->distance, 1) }} km
</span>
</div>
@endif
</x-slot>
</x-universal-listing-card>
@endforeach
</x-slot>
</x-universal-listing>
{{-- JavaScript for Location Services (GPS Integration) --}}
<script>
document.addEventListener('livewire:init', () => {
Livewire.on('request-location', () => {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
function(position) {
Livewire.dispatch('locationReceived', {
lat: position.coords.latitude,
lng: position.coords.longitude
});
},
function(error) {
let message = 'Unable to get your location.';
switch(error.code) {
case error.PERMISSION_DENIED:
message = 'Location access denied by user.';
break;
case error.POSITION_UNAVAILABLE:
message = 'Location information is unavailable.';
break;
case error.TIMEOUT:
message = 'Location request timed out.';
break;
}
Livewire.dispatch('locationError', { message: message });
},
{
enableHighAccuracy: true,
timeout: 10000,
maximumAge: 300000 // 5 minutes
}
);
} else {
Livewire.dispatch('locationError', {
message: 'Geolocation is not supported by this browser.'
});
}
});
Livewire.on('location-error', (event) => {
alert('Location Error: ' + event.message);
});
});
</script>
</div>

View File

@@ -0,0 +1,405 @@
<div class="min-h-screen bg-gray-50 dark:bg-gray-900">
{{-- Header Section --}}
<div class="bg-white dark:bg-gray-800 shadow-sm border-b border-gray-200 dark:border-gray-700">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
<div class="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-4">
{{-- Title and Stats --}}
<div class="flex-1">
<h1 class="text-2xl sm:text-3xl font-bold text-gray-900 dark:text-white">
Theme Parks
</h1>
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
{{ $parks->total() }} parks found
@if($locationEnabled)
<span class="inline-flex items-center ml-2 px-2 py-1 rounded-full text-xs bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200">
<svg class="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M5.05 4.05a7 7 0 119.9 9.9L10 18.9l-4.95-4.95a7 7 0 010-9.9zM10 11a2 2 0 100-4 2 2 0 000 4z" clip-rule="evenodd"></path>
</svg>
Location enabled
</span>
@endif
</p>
</div>
{{-- Location Controls --}}
<div class="flex items-center gap-3">
@if(!$locationEnabled)
<button
wire:click="enableLocation"
wire:loading.attr="disabled"
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
>
<svg wire:loading.remove wire:target="enableLocation" class="w-4 h-4 mr-2" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M5.05 4.05a7 7 0 119.9 9.9L10 18.9l-4.95-4.95a7 7 0 010-9.9zM10 11a2 2 0 100-4 2 2 0 000 4z" clip-rule="evenodd"></path>
</svg>
<svg wire:loading wire:target="enableLocation" class="animate-spin w-4 h-4 mr-2" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="m4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
<span wire:loading.remove wire:target="enableLocation">Find Parks Near Me</span>
<span wire:loading wire:target="enableLocation">Getting Location...</span>
</button>
@endif
{{-- Filters Toggle --}}
<button
wire:click="toggleFilters"
class="inline-flex items-center px-4 py-2 border border-gray-300 dark:border-gray-600 text-sm font-medium rounded-md text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
>
<svg class="w-4 h-4 mr-2" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M3 3a1 1 0 011-1h12a1 1 0 011 1v3a1 1 0 01-.293.707L12 11.414V15a1 1 0 01-.293.707l-2 2A1 1 0 018 17v-5.586L3.293 6.707A1 1 0 013 6V3z" clip-rule="evenodd"></path>
</svg>
Filters
@if(count($activeFilters) > 0)
<span class="ml-2 inline-flex items-center px-2 py-0.5 rounded-full text-xs bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200">
{{ count($activeFilters) }}
</span>
@endif
</button>
</div>
</div>
{{-- Search Bar --}}
<div class="mt-6">
<div class="relative">
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<svg class="h-5 w-5 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z" clip-rule="evenodd"></path>
</svg>
</div>
<input
wire:model.live.debounce.300ms="search"
type="text"
placeholder="Search parks by name, location, operator, or type..."
class="block w-full pl-10 pr-3 py-3 border border-gray-300 dark:border-gray-600 rounded-lg leading-5 bg-white dark:bg-gray-800 text-gray-900 dark:text-white placeholder-gray-500 dark:placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
>
</div>
</div>
{{-- Active Filters Display --}}
@if(count($activeFilters) > 0)
<div class="mt-4 flex flex-wrap items-center gap-2">
<span class="text-sm text-gray-600 dark:text-gray-400">Active filters:</span>
@foreach($activeFilters as $filter)
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200">
{{ $filter }}
</span>
@endforeach
<button
wire:click="clearFilters"
class="inline-flex items-center px-3 py-1 rounded-full text-sm bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-200 hover:bg-gray-200 dark:hover:bg-gray-600"
>
Clear all
</button>
</div>
@endif
</div>
</div>
{{-- Filters Panel --}}
@if($showFilters)
<div class="bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
{{-- Operator Filter --}}
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Operator</label>
<select wire:model.live="operatorId" class="block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
<option value="">All Operators</option>
@foreach($operators as $operator)
<option value="{{ $operator->id }}">{{ $operator->name }}</option>
@endforeach
</select>
</div>
{{-- Region Filter --}}
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Region</label>
<select wire:model.live="region" class="block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
<option value="">All Regions</option>
@foreach($regions as $regionOption)
<option value="{{ $regionOption }}">{{ $regionOption }}</option>
@endforeach
</select>
</div>
{{-- Country Filter --}}
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Country</label>
<select wire:model.live="country" class="block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
<option value="">All Countries</option>
@foreach($countries as $countryOption)
<option value="{{ $countryOption }}">{{ $countryOption }}</option>
@endforeach
</select>
</div>
{{-- Park Type Filter --}}
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Park Type</label>
<select wire:model.live="parkType" class="block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
<option value="">All Types</option>
@foreach($parkTypes as $type)
<option value="{{ $type }}">{{ $type }}</option>
@endforeach
</select>
</div>
{{-- Opening Year Range --}}
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Opening Year</label>
<div class="grid grid-cols-2 gap-2">
<input wire:model.live="openingYearFrom" type="number" placeholder="From" min="1800" max="{{ date('Y') }}" class="block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
<input wire:model.live="openingYearTo" type="number" placeholder="To" min="1800" max="{{ date('Y') }}" class="block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
</div>
</div>
{{-- Area Range --}}
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Area (acres)</label>
<div class="grid grid-cols-2 gap-2">
<input wire:model.live="minArea" type="number" placeholder="Min" min="0" class="block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
<input wire:model.live="maxArea" type="number" placeholder="Max" min="0" class="block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
</div>
</div>
{{-- Minimum Rides --}}
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Minimum Rides</label>
<input wire:model.live="minRides" type="number" placeholder="Min rides" min="0" class="block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
</div>
{{-- Distance Filter (only if location enabled) --}}
@if($locationEnabled)
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Max Distance (km)</label>
<input wire:model.live="maxDistance" type="number" placeholder="Distance" min="1" class="block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
</div>
@endif
</div>
</div>
</div>
@endif
{{-- Sorting Controls --}}
<div class="bg-gray-50 dark:bg-gray-900 border-b border-gray-200 dark:border-gray-700">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-3">
<div class="flex items-center justify-between">
<div class="flex items-center space-x-4">
<span class="text-sm text-gray-600 dark:text-gray-400">Sort by:</span>
<div class="flex items-center space-x-2">
<button wire:click="sortBy('name')" class="inline-flex items-center px-3 py-1 rounded-md text-sm {{ $sortBy === 'name' ? 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200' : 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white' }}">
Name
@if($sortBy === 'name')
<svg class="ml-1 w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="{{ $sortDirection === 'asc' ? 'M14.707 12.707a1 1 0 01-1.414 0L10 9.414l-3.293 3.293a1 1 0 01-1.414-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 010 1.414z' : 'M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z' }}" clip-rule="evenodd"></path>
</svg>
@endif
</button>
@if($locationEnabled)
<button wire:click="sortBy('distance')" class="inline-flex items-center px-3 py-1 rounded-md text-sm {{ $sortBy === 'distance' ? 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200' : 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white' }}">
Distance
@if($sortBy === 'distance')
<svg class="ml-1 w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="{{ $sortDirection === 'asc' ? 'M14.707 12.707a1 1 0 01-1.414 0L10 9.414l-3.293 3.293a1 1 0 01-1.414-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 010 1.414z' : 'M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z' }}" clip-rule="evenodd"></path>
</svg>
@endif
</button>
@endif
<button wire:click="sortBy('rides_count')" class="inline-flex items-center px-3 py-1 rounded-md text-sm {{ $sortBy === 'rides_count' ? 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200' : 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white' }}">
Rides
@if($sortBy === 'rides_count')
<svg class="ml-1 w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="{{ $sortDirection === 'asc' ? 'M14.707 12.707a1 1 0 01-1.414 0L10 9.414l-3.293 3.293a1 1 0 01-1.414-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 010 1.414z' : 'M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z' }}" clip-rule="evenodd"></path>
</svg>
@endif
</button>
<button wire:click="sortBy('opening_date')" class="inline-flex items-center px-3 py-1 rounded-md text-sm {{ $sortBy === 'opening_date' ? 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200' : 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white' }}">
Opening Date
@if($sortBy === 'opening_date')
<svg class="ml-1 w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="{{ $sortDirection === 'asc' ? 'M14.707 12.707a1 1 0 01-1.414 0L10 9.414l-3.293 3.293a1 1 0 01-1.414-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 010 1.414z' : 'M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z' }}" clip-rule="evenodd"></path>
</svg>
@endif
</button>
</div>
</div>
</div>
</div>
</div>
{{-- Parks Grid --}}
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
@if($parks->count() > 0)
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
@foreach($parks as $park)
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md hover:shadow-lg transition-shadow duration-200 overflow-hidden">
{{-- Park Image --}}
<div class="aspect-w-16 aspect-h-9 bg-gray-200 dark:bg-gray-700">
@if($park->photos->count() > 0)
<img src="{{ $park->photos->first()->url }}" alt="{{ $park->name }}" class="w-full h-48 object-cover">
@else
<div class="w-full h-48 flex items-center justify-center">
<svg class="w-12 h-12 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M4 3a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V5a2 2 0 00-2-2H4zm12 12H4l4-8 3 6 2-4 3 6z" clip-rule="evenodd"></path>
</svg>
</div>
@endif
</div>
{{-- Park Info --}}
<div class="p-4">
<div class="flex items-start justify-between">
<div class="flex-1 min-w-0">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white truncate">
<a href="{{ route('parks.show', $park) }}" class="hover:text-blue-600 dark:hover:text-blue-400">
{{ $park->name }}
</a>
</h3>
@if($park->location)
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1">
{{ $park->location->city }}, {{ $park->location->state }}
@if($park->location->country !== 'United States')
, {{ $park->location->country }}
@endif
</p>
@endif
@if($park->operator)
<p class="text-sm text-gray-500 dark:text-gray-500 mt-1">
{{ $park->operator->name }}
</p>
@endif
</div>
@if($locationEnabled && isset($park->distance))
<div class="ml-2 flex-shrink-0">
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200">
{{ number_format($park->distance, 1) }} km
</span>
</div>
@endif
</div>
{{-- Park Stats --}}
<div class="mt-3 flex items-center justify-between text-sm text-gray-600 dark:text-gray-400">
<div class="flex items-center space-x-4">
<span class="flex items-center">
<svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20">
<path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
{{ $park->rides_count }} rides
</span>
@if($park->reviews_count > 0)
<span class="flex items-center">
<svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20">
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"></path>
</svg>
{{ $park->reviews_count }} reviews
</span>
@endif
</div>
@if($park->opening_date)
<span class="text-xs">
{{ $park->opening_date->format('Y') }}
</span>
@endif
</div>
{{-- Park Type --}}
@if($park->park_type)
<div class="mt-2">
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-200">
{{ $park->park_type }}
</span>
</div>
@endif
</div>
</div>
@endforeach
</div>
{{-- Pagination --}}
<div class="mt-8">
{{ $parks->links() }}
</div>
@else
{{-- Empty State --}}
<div class="text-center py-12">
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"></path>
</svg>
<h3 class="mt-2 text-sm font-medium text-gray-900 dark:text-white">No parks found</h3>
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
Try adjusting your search criteria or filters.
</p>
@if(count($activeFilters) > 0)
<div class="mt-6">
<button
wire:click="clearFilters"
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
>
Clear
Clear All Filters
</button>
</div>
@endif
</div>
@endif
</div>
{{-- Loading State --}}
<div wire:loading.delay class="fixed inset-0 bg-gray-900 bg-opacity-50 flex items-center justify-center z-50">
<div class="bg-white dark:bg-gray-800 rounded-lg p-6 flex items-center space-x-3">
<svg class="animate-spin h-5 w-5 text-blue-600" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="m4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
<span class="text-gray-900 dark:text-white">Loading parks...</span>
</div>
</div>
</div>
{{-- JavaScript for Location Services --}}
<script>
document.addEventListener('livewire:init', () => {
Livewire.on('request-location', () => {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
function(position) {
Livewire.dispatch('locationReceived', {
lat: position.coords.latitude,
lng: position.coords.longitude
});
},
function(error) {
let message = 'Unable to get your location.';
switch(error.code) {
case error.PERMISSION_DENIED:
message = 'Location access denied by user.';
break;
case error.POSITION_UNAVAILABLE:
message = 'Location information is unavailable.';
break;
case error.TIMEOUT:
message = 'Location request timed out.';
break;
}
Livewire.dispatch('locationError', { message: message });
},
{
enableHighAccuracy: true,
timeout: 10000,
maximumAge: 300000 // 5 minutes
}
);
} else {
Livewire.dispatch('locationError', {
message: 'Geolocation is not supported by this browser.'
});
}
});
Livewire.on('location-error', (event) => {
alert('Location Error: ' + event.message);
});
});
</script>

View File

@@ -0,0 +1,31 @@
{{-- ThrillWiki Reusable Component: ParksLocationSearch --}}
<div class="thrillwiki-component"
x-data="{ loading: false }"
wire:loading.class="opacity-50">
{{-- Component Header --}}
<div class="component-header mb-4">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">
ParksLocationSearch
</h3>
</div>
{{-- Component Content --}}
<div class="component-content">
<p class="text-gray-600 dark:text-gray-400">
ThrillWiki component content goes here.
</p>
{{-- Example interactive element --}}
<button wire:click="$refresh"
class="mt-4 px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors">
Refresh Component
</button>
</div>
{{-- Loading State --}}
<div wire:loading wire:target="$refresh"
class="absolute inset-0 bg-white bg-opacity-75 flex items-center justify-center">
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
</div>
</div>

View File

@@ -0,0 +1,31 @@
{{-- ThrillWiki Reusable Component: ParksMapView --}}
<div class="thrillwiki-component"
x-data="{ loading: false }"
wire:loading.class="opacity-50">
{{-- Component Header --}}
<div class="component-header mb-4">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">
ParksMapView
</h3>
</div>
{{-- Component Content --}}
<div class="component-content">
<p class="text-gray-600 dark:text-gray-400">
ThrillWiki component content goes here.
</p>
{{-- Example interactive element --}}
<button wire:click="$refresh"
class="mt-4 px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors">
Refresh Component
</button>
</div>
{{-- Loading State --}}
<div wire:loading wire:target="$refresh"
class="absolute inset-0 bg-white bg-opacity-75 flex items-center justify-center">
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
</div>
</div>

View File

@@ -0,0 +1,10 @@
{{-- ThrillWiki Component: RegionalParksListing --}}
<div class="thrillwiki-component">
<h3 class="text-lg font-semibold mb-4 text-gray-900 dark:text-white">
RegionalParksListing
</h3>
<p class="text-gray-600 dark:text-gray-400">
ThrillWiki component content goes here.
</p>
</div>

View File

@@ -1,31 +1,288 @@
{{-- ThrillWiki Reusable Component: RidesFilters --}}
<div class="thrillwiki-component"
x-data="{ loading: false }"
wire:loading.class="opacity-50">
{{-- Component Header --}}
<div class="component-header mb-4">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">
RidesFilters
</h3>
</div>
{{-- Component Content --}}
<div class="component-content">
<p class="text-gray-600 dark:text-gray-400">
ThrillWiki component content goes here.
</p>
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700">
<!-- Filter Header -->
<div class="p-4 border-b border-gray-200 dark:border-gray-700">
<div class="flex items-center justify-between">
<div class="flex items-center space-x-3">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">
Filters
</h3>
@if($activeFiltersCount > 0)
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200">
{{ $activeFiltersCount }} active
</span>
@endif
</div>
<div class="flex items-center space-x-2">
@if($activeFiltersCount > 0)
<button
wire:click="clearAllFilters"
class="text-sm text-red-600 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 font-medium"
>
Clear All
</button>
@endif
<button
wire:click="toggleAdvancedFilters"
class="flex items-center space-x-1 text-sm text-gray-600 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300 font-medium"
>
<span>{{ $showAdvancedFilters ? 'Hide' : 'Show' }} Advanced</span>
<svg class="w-4 h-4 transform transition-transform {{ $showAdvancedFilters ? 'rotate-180' : '' }}"
fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
</svg>
</button>
</div>
</div>
{{-- Example interactive element --}}
<button wire:click="$refresh"
class="mt-4 px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors">
Refresh Component
</button>
<!-- Active Filters Summary -->
@if($activeFiltersCount > 0)
<div class="mt-3 flex flex-wrap gap-2">
@foreach($this->getFilterSummary() as $filter)
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-200">
{{ $filter }}
</span>
@endforeach
</div>
@endif
</div>
{{-- Loading State --}}
<div wire:loading wire:target="$refresh"
class="absolute inset-0 bg-white bg-opacity-75 flex items-center justify-center">
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
<!-- Basic Filters -->
<div class="p-4 space-y-4">
<!-- Categories -->
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Category
</label>
<div class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-6 gap-2">
@foreach($categories as $category)
<button
wire:click="setCategory('{{ $category['value'] }}')"
class="flex items-center justify-between p-2 text-sm rounded-lg border transition-colors
{{ $selectedCategory === $category['value']
? 'bg-blue-50 border-blue-200 text-blue-700 dark:bg-blue-900/20 dark:border-blue-700 dark:text-blue-300'
: 'bg-white border-gray-200 text-gray-700 hover:bg-gray-50 dark:bg-gray-700 dark:border-gray-600 dark:text-gray-300 dark:hover:bg-gray-600' }}"
>
<span class="truncate">{{ $category['label'] }}</span>
<span class="ml-1 text-xs text-gray-500 dark:text-gray-400">{{ $category['count'] }}</span>
</button>
@endforeach
</div>
</div>
<!-- Status -->
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Status
</label>
<div class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-2">
@foreach($statuses as $status)
<button
wire:click="setStatus('{{ $status['value'] }}')"
class="flex items-center justify-between p-2 text-sm rounded-lg border transition-colors
{{ $selectedStatus === $status['value']
? 'bg-green-50 border-green-200 text-green-700 dark:bg-green-900/20 dark:border-green-700 dark:text-green-300'
: 'bg-white border-gray-200 text-gray-700 hover:bg-gray-50 dark:bg-gray-700 dark:border-gray-600 dark:text-gray-300 dark:hover:bg-gray-600' }}"
>
<span class="truncate">{{ $status['label'] }}</span>
<span class="ml-1 text-xs text-gray-500 dark:text-gray-400">{{ $status['count'] }}</span>
</button>
@endforeach
</div>
</div>
</div>
</div>
<!-- Advanced Filters -->
<div class="transition-all duration-300 {{ $showAdvancedFilters ? 'block' : 'hidden' }}">
<div class="border-t border-gray-200 dark:border-gray-700 p-4 space-y-4">
<!-- Manufacturer -->
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Manufacturer
</label>
<select
wire:model.live="selectedManufacturer"
wire:change="setManufacturer($event.target.value)"
class="w-full rounded-lg border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:border-blue-500 focus:ring-blue-500"
>
<option value="">All Manufacturers</option>
@foreach($manufacturers as $manufacturer)
<option value="{{ $manufacturer['value'] }}">
{{ $manufacturer['label'] }} ({{ $manufacturer['count'] }})
</option>
@endforeach
</select>
</div>
<!-- Park -->
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Park
</label>
<select
wire:model.live="selectedPark"
wire:change="setPark($event.target.value)"
class="w-full rounded-lg border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:border-blue-500 focus:ring-blue-500"
>
<option value="">All Parks</option>
@foreach($parks as $park)
<option value="{{ $park['value'] }}">
{{ $park['label'] }} ({{ $park['count'] }})
</option>
@endforeach
</select>
</div>
<!-- Opening Year Range -->
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Opening Year (Min)
</label>
<input
type="number"
wire:model.live="minOpeningYear"
wire:change="updateYearRange"
min="{{ $yearRange['min'] }}"
max="{{ $yearRange['max'] }}"
placeholder="{{ $yearRange['min'] }}"
class="w-full rounded-lg border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:border-blue-500 focus:ring-blue-500"
>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Opening Year (Max)
</label>
<input
type="number"
wire:model.live="maxOpeningYear"
wire:change="updateYearRange"
min="{{ $yearRange['min'] }}"
max="{{ $yearRange['max'] }}"
placeholder="{{ $yearRange['max'] }}"
class="w-full rounded-lg border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:border-blue-500 focus:ring-blue-500"
>
</div>
</div>
<!-- Height Requirement Range -->
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Min Height (cm)
</label>
<input
type="number"
wire:model.live="minHeight"
wire:change="updateHeightRange"
min="{{ $heightRange['min'] }}"
max="{{ $heightRange['max'] }}"
placeholder="{{ $heightRange['min'] }}"
class="w-full rounded-lg border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:border-blue-500 focus:ring-blue-500"
>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Max Height (cm)
</label>
<input
type="number"
wire:model.live="maxHeight"
wire:change="updateHeightRange"
min="{{ $heightRange['min'] }}"
max="{{ $heightRange['max'] }}"
placeholder="{{ $heightRange['max'] }}"
class="w-full rounded-lg border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:border-blue-500 focus:ring-blue-500"
>
</div>
</div>
<!-- Range Indicators -->
<div class="text-xs text-gray-500 dark:text-gray-400 space-y-1">
<div>Available years: {{ $yearRange['min'] }} - {{ $yearRange['max'] }}</div>
<div>Available heights: {{ $heightRange['min'] }}cm - {{ $heightRange['max'] }}cm</div>
</div>
</div>
</div>
<!-- Mobile-Optimized Filter Actions -->
<div class="sm:hidden border-t border-gray-200 dark:border-gray-700 p-4">
<div class="flex space-x-3">
@if($activeFiltersCount > 0)
<button
wire:click="clearAllFilters"
class="flex-1 px-4 py-2 text-sm font-medium text-red-600 bg-red-50 border border-red-200 rounded-lg hover:bg-red-100 dark:bg-red-900/20 dark:border-red-700 dark:text-red-400 dark:hover:bg-red-900/30"
>
Clear All ({{ $activeFiltersCount }})
</button>
@endif
<button
wire:click="toggleAdvancedFilters"
class="flex-1 px-4 py-2 text-sm font-medium text-gray-700 bg-gray-50 border border-gray-200 rounded-lg hover:bg-gray-100 dark:bg-gray-700 dark:border-gray-600 dark:text-gray-300 dark:hover:bg-gray-600"
>
{{ $showAdvancedFilters ? 'Hide' : 'Show' }} Advanced
</button>
</div>
</div>
<!-- Loading State -->
<div wire:loading.flex class="absolute inset-0 bg-white/75 dark:bg-gray-800/75 items-center justify-center rounded-lg">
<div class="flex items-center space-x-2 text-gray-600 dark:text-gray-400">
<svg class="animate-spin h-5 w-5" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
<span class="text-sm">Updating filters...</span>
</div>
</div>
</div>
<!-- Screen-Agnostic Responsive Styles -->
<style>
/* Mobile-first responsive design */
@media (max-width: 320px) {
.grid-cols-2 { grid-template-columns: repeat(1, minmax(0, 1fr)); }
}
@media (min-width: 480px) {
.sm\:grid-cols-3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 768px) {
.lg\:grid-cols-4 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (min-width: 1024px) {
.lg\:grid-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
@media (min-width: 1280px) {
.xl\:grid-cols-6 { grid-template-columns: repeat(5, minmax(0, 1fr)); }
}
@media (min-width: 1440px) {
.xl\:grid-cols-6 { grid-template-columns: repeat(6, minmax(0, 1fr)); }
}
@media (min-width: 1920px) {
.xl\:grid-cols-6 { grid-template-columns: repeat(8, minmax(0, 1fr)); }
}
@media (min-width: 2560px) {
.xl\:grid-cols-6 { grid-template-columns: repeat(10, minmax(0, 1fr)); }
}
/* Touch-friendly targets for mobile */
@media (max-width: 768px) {
button, select, input {
min-height: 44px;
}
}
/* High DPI display optimizations */
@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
.border { border-width: 0.5px; }
}
</style>

View File

@@ -0,0 +1,19 @@
<div>
{{-- Universal Listing System Integration --}}
<x-universal-listing
:entity-type="$entityType"
:items="$items"
:search="$search"
:categories="$categories"
:opening-year-from="$openingYearFrom"
:opening-year-to="$openingYearTo"
:sort-by="$sortBy"
:view-mode="$viewMode"
wire:model.live="search"
wire:model.live="categories"
wire:model.live="openingYearFrom"
wire:model.live="openingYearTo"
wire:model.live="sortBy"
wire:model.live="viewMode"
/>
</div>

View File

@@ -0,0 +1,47 @@
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Manufacturers - ThrillWiki</title>
@vite(['resources/css/app.css', 'resources/js/app.js'])
@livewireStyles
</head>
<body class="bg-gray-50 dark:bg-gray-900">
<div class="min-h-screen">
<!-- Header -->
<header class="bg-white dark:bg-gray-800 shadow">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between h-16">
<div class="flex items-center">
<h1 class="text-xl font-semibold text-gray-900 dark:text-white">
ThrillWiki
</h1>
</div>
<nav class="flex space-x-8">
<a href="{{ route('home') }}" class="text-gray-500 hover:text-gray-700 dark:text-gray-300 dark:hover:text-white px-3 py-2 text-sm font-medium">
Home
</a>
<a href="{{ route('parks.index') }}" class="text-gray-500 hover:text-gray-700 dark:text-gray-300 dark:hover:text-white px-3 py-2 text-sm font-medium">
Parks
</a>
<a href="{{ route('rides.index') }}" class="text-gray-500 hover:text-gray-700 dark:text-gray-300 dark:hover:text-white px-3 py-2 text-sm font-medium">
Rides
</a>
<a href="{{ route('manufacturers.index') }}" class="text-orange-600 hover:text-orange-700 dark:text-orange-400 dark:hover:text-orange-300 px-3 py-2 text-sm font-medium border-b-2 border-orange-600 dark:border-orange-400">
Manufacturers
</a>
</nav>
</div>
</div>
</header>
<!-- Main Content -->
<main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
@livewire('manufacturers-listing-universal')
</main>
</div>
@livewireScripts
</body>
</html>