mirror of
https://github.com/pacnpal/thrillwiki_laravel.git
synced 2025-12-20 02:31:09 -05:00
- 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.
513 lines
31 KiB
PHP
513 lines
31 KiB
PHP
@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> |