@extends('layouts.dashboard') @section('content') @php /** * @var array $reviews rows joined with restaurant name/slug + the reviewer's own history * @var string $filter active status filter ('' = all) * @var int $frating active rating filter (0 = any) * @var int $frestaurant active restaurant filter (0 = all) * @var array $restaurants restaurants that carry at least one review * @var array $counts [status => count] * @var array $statuses allowed statuses * @var array $pager pagination metadata */ $frating = (int) ($frating ?? 0); $frestaurant = (int) ($frestaurant ?? 0); $restaurants = $restaurants ?? []; $tabs = array_merge(['' => t_raw('common.all')], array_combine($statuses, array_map(static fn ($s) => t_raw("status.review.{$s}"), $statuses))); $pillClass = ['approved' => 'qm-pill-green', 'pending' => 'qm-pill-amber']; $hasFilters = $frating > 0 || $frestaurant > 0; // Tabs must keep the other two filters; the actions must come back to all three. $carry = array_filter(['rating' => $frating ?: '', 'restaurant' => $frestaurant ?: '']); $backQuery = http_build_query(array_filter( ['status' => $filter, 'rating' => $frating ?: '', 'restaurant' => $frestaurant ?: ''], static fn ($v) => (string) $v !== '' )); // Current filters, so the Export link carries the same view the admin is looking at. $qs = http_build_query(array_filter([ 'status' => $filter, 'rating' => $frating ?: '', 'restaurant' => $frestaurant ?: '', ], static fn ($v) => (string) $v !== '')); @endphp @php /* .qm-d2: the dashboard density standard (app.css ยง35) */ @endphp

{{ t_raw('admin.reviews.title') }}

@if (!empty($counts['pending'])){{ t_raw('admin.comments.pending_badge', [':count' => (int) $counts['pending']]) }}@endif
{{ t_raw('common.export_csv') }}
@partial('filter-collapse') @if ($filter !== '')@endif
@php /* Rating renders as star icons and Review is free text, so those two th's carry a data-sort-value / no sort control respectively. */ @endphp @if ($reviews) @foreach ($reviews as $rv) @php $customerName = trim((string) ($rv['customer_name'] ?? '')) ?: (trim((string) ($rv['author_name'] ?? '')) ?: t_raw('common.customer')); $authorCount = (int) ($rv['author_reviews_c'] ?? 0); $authorAvg = $rv['author_avg_rating'] !== null ? (float) $rv['author_avg_rating'] : null; // A repeat reviewer whose average sits at or below two stars is the pattern // a moderator is looking for when a single bad review lands. $isSerial = $authorCount >= 3 && $authorAvg !== null && $authorAvg <= 2.0; $isApproved = $rv['status'] === 'approved'; @endphp @endforeach @else @partial('table-empty', ['colspan' => 7, 'icon' => 'fa-star', 'msg' => ($filter !== '' || $hasFilters) ? t('admin.reviews.empty_filtered') : t('admin.reviews.empty_none')]) @endif
{{ t_raw('nav.restaurants') }}{{ t_raw('common.customer') }}{{ t_raw('common.rating') }}{{ t_raw('admin.reviews.col_review') }}{{ t_raw('common.status') }}{{ t_raw('admin.reviews.col_posted') }}{{ t_raw('common.actions') }}
@if (!empty($rv['restaurant_slug']))
{!! restaurant_logo_html(['logo' => $rv['restaurant_logo'] ?? '', 'name' => $rv['restaurant_name'] ?? ''], 'md') !!}
@else {{ t_raw('admin.reviews.deleted_restaurant') }} @endif
@php /* Reviewer history sits under the name: how many reviews this account has written and what it averages, so a lone one-star can be read in context without opening the record. */ @endphp
{!! brandmark_html($rv['customer_avatar'] ?? null, $customerName, 'md', 'qm-brandmark--round') !!}
{{ $customerName }} @if ($authorCount > 0 && $authorAvg !== null) {{ t_raw('admin.reviews.author_history', [':count' => $authorCount, ':avg' => number_format($authorAvg, 1)]) }} @endif @if ($isSerial)
{{ t_raw('admin.reviews.pill_serial_complainer') }}
@endif
{!! rating_stars_html((float) (int) $rv['rating']) !!}
{!! or_dash($rv['comment'] ?? null) !!}
{{ status_label('review', $rv['status']) }} {!! fmt_day($rv['created_at']) !!} @partial('row-actions', ['actions' => [ [ 'label' => $isApproved ? t_raw('admin.reviews.action_hide') : t_raw('admin.campaigns.approve'), 'icon' => $isApproved ? 'fa-eye-slash' : 'fa-check', 'primary' => true, 'form' => [ 'action' => '/admin/reviews/status', 'fields' => ['id' => (int) $rv['id'], 'status' => $isApproved ? 'pending' : 'approved', 'filter' => $backQuery], ], ], ['label' => t_raw('admin.reviews.aria_edit'), 'icon' => 'fa-pen', 'href' => '/admin/reviews/' . (int) $rv['id']], !empty($rv['restaurant_slug']) ? ['label' => t_raw('admin.restaurants.aria_view_public'), 'icon' => 'fa-eye', 'href' => '/restaurant/' . rawurlencode((string) $rv['restaurant_slug'])] : null, [ 'label' => t_raw('admin.reviews.aria_delete'), 'icon' => 'fa-trash-alt', 'danger' => true, 'form' => [ 'action' => '/admin/reviews/delete', 'fields' => ['id' => (int) $rv['id'], 'filter' => $backQuery], 'confirm' => t_raw('admin.reviews.confirm_delete'), ], ], ]])
@partial('table-toolbar', ['pager' => $pager, 'base' => '/admin/reviews'])
@php /* /.qm-d2 */ @endphp @endsection