Portatour Reviews -

$reviews = PortaTourReview::where('tour_id', $tourId) ->where('is_approved', true) ->with('user:id,name,avatar') ->orderBy('created_at', 'desc') ->paginate(10); // Aggregate rating breakdown $stats = [ 'average_rating' => PortaTourReview::where('tour_id', $tourId) ->where('is_approved', true) ->avg('rating'), 'total_reviews' => $reviews->total(), 'rating_counts' => PortaTourReview::where('tour_id', $tourId) ->where('is_approved', true) ->selectRaw('rating, count(*) as count') ->groupBy('rating') ->pluck('count', 'rating') ];

return $this->belongsTo(User::class);

export default PortaTourReviews; // Admin routes Route::get('/admin/reviews/pending', [AdminReviewController::class, 'pending']); Route::patch('/admin/reviews/id/approve', [AdminReviewController::class, 'approve']); Route::delete('/admin/reviews/id', [AdminReviewController::class, 'destroy']); Admin view filters unapproved reviews, can approve, delete, or reply. 5. SEO & Schema.org Markup Add to tour page: portatour reviews

return ( <div className="portatour-reviews"> <div className="reviews-header"> <h2>Customer Reviews</h2> <div className="rating-summary"> <span className="average">stats?.average_rating.toFixed(1)</span> <StarRating rating=stats?.average_rating /> <span>(stats?.total_reviews reviews)</span> </div> </div>

<div className="reviews-list"> reviews.map(review => ( <div key=review.id className="review-card"> <div className="reviewer-info"> <img src=review.user.avatar alt=review.user.name /> <strong>review.user.name</strong> review.is_verified_purchase && <span className="verified">✓ Verified Tour</span> </div> <StarRating rating=review.rating readonly /> <h3>review.title</h3> <p>review.comment</p> review.images?.length > 0 && ( <div className="review-images"> review.images.map(img => <img key=img src=img />) </div> ) <button onClick=() => markHelpful(review.id)>👍 Helpful (review.helpful_count)</button> </div> )) </div> $reviews = PortaTourReview::where('tour_id'

// prevent duplicate helpful votes (pivot table omitted for brevity) $this->increment('helpful_count');

"@context": "https://schema.org", "@type": "Product", "name": "Paris Walking Tour", "aggregateRating": "@type": "AggregateRating", "ratingValue": "4.8", "reviewCount": "127" , "review": [ "@type": "Review", "author": "John Doe", "datePublished": "2025-03-10", "reviewRating": "@type": "Rating", "ratingValue": "5" , "reviewBody": "Amazing guide!" ] count(*) as count') -&gt

</script> ✅ Verified purchase only (via booking ID) ✅ Star rating (1–5) + title + comment ✅ Image upload (max 5) ✅ Helpful button ✅ Admin approval workflow ✅ Average rating & breakdown ✅ Sort by newest / highest rated ✅ SEO structured data