mirror of
https://github.com/pacnpal/thrillwiki_laravel.git
synced 2025-12-20 07:31:09 -05:00
- Add RideReviewComponent for submitting reviews - Star rating input with real-time validation - Rate limiting and anti-spam measures - Edit capabilities for own reviews - Add RideReviewListComponent for displaying reviews - Paginated list with sort/filter options - Helpful vote functionality - Statistics display with rating distribution - Add ReviewModerationComponent for review management - Review queue with status filters - Approve/reject functionality - Batch actions support - Edit capabilities - Update Memory Bank documentation - Document component implementations - Track feature completion - Update technical decisions
190 lines
8.4 KiB
PHP
190 lines
8.4 KiB
PHP
<div class="space-y-6">
|
|
{{-- Message --}}
|
|
@if ($message)
|
|
<div @class([
|
|
'p-4 mb-4 rounded-lg',
|
|
'bg-green-100 dark:bg-green-800 text-green-700 dark:text-green-200' => !str_contains($message, 'error') && !str_contains($message, 'cannot'),
|
|
'bg-red-100 dark:bg-red-800 text-red-700 dark:text-red-200' => str_contains($message, 'error') || str_contains($message, 'cannot'),
|
|
])>
|
|
{{ $message }}
|
|
</div>
|
|
@endif
|
|
|
|
{{-- Statistics Panel --}}
|
|
<div x-data="{ open: @entangle('showStats') }" class="bg-white dark:bg-gray-800 rounded-lg shadow">
|
|
<div class="p-4 border-b border-gray-200 dark:border-gray-700">
|
|
<button
|
|
type="button"
|
|
@click="open = !open"
|
|
class="flex justify-between items-center w-full"
|
|
>
|
|
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100">
|
|
Review Statistics
|
|
</h3>
|
|
<span class="transform" :class="{ 'rotate-180': open }">
|
|
▼
|
|
</span>
|
|
</button>
|
|
</div>
|
|
<div x-show="open" class="p-4">
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
<div class="text-center">
|
|
<div class="text-3xl font-bold text-gray-900 dark:text-gray-100">
|
|
{{ $statistics['average'] }}
|
|
</div>
|
|
<div class="text-sm text-gray-500">
|
|
Average Rating
|
|
</div>
|
|
</div>
|
|
<div class="text-center">
|
|
<div class="text-3xl font-bold text-gray-900 dark:text-gray-100">
|
|
{{ $statistics['total'] }}
|
|
</div>
|
|
<div class="text-sm text-gray-500">
|
|
Total Reviews
|
|
</div>
|
|
</div>
|
|
<div class="col-span-1 md:col-span-1">
|
|
@foreach (range(5, 1) as $rating)
|
|
<div class="flex items-center">
|
|
<span class="w-8 text-sm text-gray-600 dark:text-gray-400">
|
|
{{ $rating }}★
|
|
</span>
|
|
<div
|
|
x-data="{ width: '{{ $statistics['total'] > 0 ? number_format(($statistics['distribution'][$rating] / $statistics['total']) * 100, 1) : 0 }}%' }"
|
|
class="flex-1 h-4 mx-2 bg-gray-200 dark:bg-gray-700 rounded relative overflow-hidden"
|
|
>
|
|
@if ($statistics['total'] > 0)
|
|
<div
|
|
class="h-4 bg-yellow-400 rounded absolute inset-y-0 left-0"
|
|
:style="{ width }"
|
|
></div>
|
|
@endif
|
|
</div>
|
|
<span class="w-8 text-sm text-gray-600 dark:text-gray-400">
|
|
{{ $statistics['distribution'][$rating] }}
|
|
</span>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Controls --}}
|
|
<div class="flex flex-wrap gap-4 items-center justify-between">
|
|
{{-- Sort Controls --}}
|
|
<div class="flex gap-2">
|
|
<button
|
|
wire:click="sortBy('created_at')"
|
|
@class([
|
|
'px-3 py-2 text-sm font-medium rounded-md',
|
|
'bg-primary-100 text-primary-700 dark:bg-primary-800 dark:text-primary-200' => $sortField === 'created_at',
|
|
'text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700' => $sortField !== 'created_at',
|
|
])
|
|
>
|
|
Date
|
|
@if ($sortField === 'created_at')
|
|
<span>{{ $sortDirection === 'asc' ? '↑' : '↓' }}</span>
|
|
@endif
|
|
</button>
|
|
<button
|
|
wire:click="sortBy('rating')"
|
|
@class([
|
|
'px-3 py-2 text-sm font-medium rounded-md',
|
|
'bg-primary-100 text-primary-700 dark:bg-primary-800 dark:text-primary-200' => $sortField === 'rating',
|
|
'text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700' => $sortField !== 'rating',
|
|
])
|
|
>
|
|
Rating
|
|
@if ($sortField === 'rating')
|
|
<span>{{ $sortDirection === 'asc' ? '↑' : '↓' }}</span>
|
|
@endif
|
|
</button>
|
|
</div>
|
|
|
|
{{-- Rating Filter --}}
|
|
<div class="flex gap-2">
|
|
@foreach (range(1, 5) as $rating)
|
|
<button
|
|
wire:click="filterByRating({{ $rating }})"
|
|
@class([
|
|
'px-3 py-2 text-sm font-medium rounded-md',
|
|
'bg-yellow-100 text-yellow-700 dark:bg-yellow-800 dark:text-yellow-200' => $ratingFilter === $rating,
|
|
'text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700' => $ratingFilter !== $rating,
|
|
])
|
|
>
|
|
{{ $rating }}★
|
|
</button>
|
|
@endforeach
|
|
@if ($ratingFilter)
|
|
<button
|
|
wire:click="filterByRating(null)"
|
|
class="px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700 rounded-md"
|
|
>
|
|
Clear
|
|
</button>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Reviews List --}}
|
|
<div class="space-y-4">
|
|
@forelse ($reviews as $review)
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
|
<div class="flex justify-between items-start">
|
|
<div>
|
|
<div class="flex items-center gap-2">
|
|
<div class="text-yellow-400 text-xl">
|
|
@for ($i = 1; $i <= 5; $i++)
|
|
<span @class(['opacity-40' => $i > $review->rating])>★</span>
|
|
@endfor
|
|
</div>
|
|
@if ($review->title)
|
|
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100">
|
|
{{ $review->title }}
|
|
</h3>
|
|
@endif
|
|
</div>
|
|
<p class="mt-2 text-gray-600 dark:text-gray-400">
|
|
{{ $review->content }}
|
|
</p>
|
|
</div>
|
|
<div class="text-sm text-gray-500">
|
|
{{ $review->created_at->diffForHumans() }}
|
|
</div>
|
|
</div>
|
|
<div class="mt-4 flex items-center justify-between">
|
|
<div class="text-sm text-gray-500">
|
|
By {{ $review->user->name }}
|
|
</div>
|
|
<button
|
|
wire:click="toggleHelpfulVote({{ $review->id }})"
|
|
@class([
|
|
'flex items-center gap-2 px-3 py-2 text-sm font-medium rounded-md',
|
|
'bg-green-100 text-green-700 dark:bg-green-800 dark:text-green-200' => $review->helpfulVotes->contains('user_id', Auth::id()),
|
|
'text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700' => !$review->helpfulVotes->contains('user_id', Auth::id()),
|
|
])
|
|
>
|
|
<span>Helpful</span>
|
|
<span>({{ $review->helpfulVotes->count() }})</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
@empty
|
|
<div class="text-center py-12 text-gray-500 dark:text-gray-400">
|
|
@if ($ratingFilter)
|
|
No {{ $ratingFilter }}-star reviews yet.
|
|
@else
|
|
No reviews yet.
|
|
@endif
|
|
</div>
|
|
@endforelse
|
|
|
|
{{-- Pagination --}}
|
|
<div class="mt-6">
|
|
{{ $reviews->links() }}
|
|
</div>
|
|
</div>
|
|
</div>
|